| /* |
| * Copyright (c) 1995, 2015, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.applet; |
| |
| import java.applet.*; |
| import java.awt.*; |
| import java.awt.event.*; |
| import java.io.*; |
| import java.lang.ref.WeakReference; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.net.JarURLConnection; |
| import java.net.SocketPermission; |
| import java.net.URL; |
| import java.security.*; |
| import java.util.*; |
| import java.util.Locale; |
| import java.util.concurrent.LinkedBlockingQueue; |
| import sun.awt.AWTAccessor; |
| import sun.awt.AppContext; |
| import sun.awt.EmbeddedFrame; |
| import sun.awt.SunToolkit; |
| import sun.awt.util.PerformanceLogger; |
| import sun.misc.ManagedLocalsThread; |
| import sun.security.util.SecurityConstants; |
| |
| /** |
| * Applet panel class. The panel manages and manipulates the |
| * applet as it is being loaded. It forks a separate thread in a new |
| * thread group to call the applet's init(), start(), stop(), and |
| * destroy() methods. |
| * |
| * @author Arthur van Hoff |
| */ |
| @SuppressWarnings("serial") // JDK implementation class |
| public |
| abstract class AppletPanel extends Panel implements AppletStub, Runnable { |
| |
| /** |
| * The applet (if loaded). |
| */ |
| Applet applet; |
| |
| |
| /** |
| * The classloader for the applet. |
| */ |
| protected AppletClassLoader loader; |
| |
| /* applet event ids */ |
| public static final int APPLET_DISPOSE = 0; |
| public static final int APPLET_LOAD = 1; |
| public static final int APPLET_INIT = 2; |
| public static final int APPLET_START = 3; |
| public static final int APPLET_STOP = 4; |
| public static final int APPLET_DESTROY = 5; |
| public static final int APPLET_QUIT = 6; |
| public static final int APPLET_ERROR = 7; |
| |
| /* send to the parent to force relayout */ |
| public static final int APPLET_RESIZE = 51234; |
| |
| /* sent to a (distant) parent to indicate that the applet is being |
| * loaded or as completed loading |
| */ |
| public static final int APPLET_LOADING = 51235; |
| public static final int APPLET_LOADING_COMPLETED = 51236; |
| |
| /** |
| * The current status. One of: |
| * APPLET_DISPOSE, |
| * APPLET_LOAD, |
| * APPLET_INIT, |
| * APPLET_START, |
| * APPLET_STOP, |
| * APPLET_DESTROY, |
| * APPLET_ERROR. |
| */ |
| protected int status; |
| |
| /** |
| * The thread for the applet. |
| */ |
| protected Thread handler; |
| |
| |
| /** |
| * The initial applet size. |
| */ |
| Dimension defaultAppletSize = new Dimension(10, 10); |
| |
| /** |
| * The current applet size. |
| */ |
| Dimension currentAppletSize = new Dimension(10, 10); |
| |
| /** |
| * The thread to use during applet loading |
| */ |
| |
| Thread loaderThread = null; |
| |
| /** |
| * Flag to indicate that a loading has been cancelled |
| */ |
| boolean loadAbortRequest = false; |
| |
| /* abstract classes */ |
| protected abstract String getCode(); |
| protected abstract String getJarFiles(); |
| |
| @Override |
| public abstract int getWidth(); |
| @Override |
| public abstract int getHeight(); |
| public abstract boolean hasInitialFocus(); |
| |
| private static int threadGroupNumber = 0; |
| |
| protected void setupAppletAppContext() { |
| // do nothing |
| } |
| |
| /* |
| * Creates a thread to run the applet. This method is called |
| * each time an applet is loaded and reloaded. |
| */ |
| synchronized void createAppletThread() { |
| // Create a thread group for the applet, and start a new |
| // thread to load the applet. |
| String nm = "applet-" + getCode(); |
| loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey()); |
| loader.grab(); // Keep this puppy around! |
| |
| // 4668479: Option to turn off codebase lookup in AppletClassLoader |
| // during resource requests. [stanley.ho] |
| String param = getParameter("codebase_lookup"); |
| |
| if (param != null && param.equals("false")) |
| loader.setCodebaseLookup(false); |
| else |
| loader.setCodebaseLookup(true); |
| |
| |
| ThreadGroup appletGroup = loader.getThreadGroup(); |
| handler = new ManagedLocalsThread(appletGroup, this, "thread " + nm); |
| // set the context class loader for this thread |
| AccessController.doPrivileged(new PrivilegedAction<Object>() { |
| @Override |
| public Object run() { |
| handler.setContextClassLoader(loader); |
| return null; |
| } |
| }); |
| handler.start(); |
| } |
| |
| void joinAppletThread() throws InterruptedException { |
| if (handler != null) { |
| handler.join(); |
| handler = null; |
| } |
| } |
| |
| void release() { |
| if (loader != null) { |
| loader.release(); |
| loader = null; |
| } |
| } |
| |
| /** |
| * Construct an applet viewer and start the applet. |
| */ |
| public void init() { |
| try { |
| // Get the width (if any) |
| defaultAppletSize.width = getWidth(); |
| currentAppletSize.width = defaultAppletSize.width; |
| |
| // Get the height (if any) |
| defaultAppletSize.height = getHeight(); |
| currentAppletSize.height = defaultAppletSize.height; |
| |
| } catch (NumberFormatException e) { |
| // Turn on the error flag and let TagAppletPanel |
| // do the right thing. |
| status = APPLET_ERROR; |
| showAppletStatus("badattribute.exception"); |
| showAppletLog("badattribute.exception"); |
| showAppletException(e); |
| } |
| |
| setLayout(new BorderLayout()); |
| |
| createAppletThread(); |
| } |
| |
| /** |
| * Minimum size |
| */ |
| @Override |
| @SuppressWarnings("deprecation") |
| public Dimension minimumSize() { |
| return new Dimension(defaultAppletSize.width, |
| defaultAppletSize.height); |
| } |
| |
| /** |
| * Preferred size |
| */ |
| @Override |
| @SuppressWarnings("deprecation") |
| public Dimension preferredSize() { |
| return new Dimension(currentAppletSize.width, |
| currentAppletSize.height); |
| } |
| |
| private AppletListener listeners; |
| |
| /** |
| * AppletEvent Queue |
| */ |
| private LinkedBlockingQueue<Integer> queue = null; |
| |
| public synchronized void addAppletListener(AppletListener l) { |
| listeners = AppletEventMulticaster.add(listeners, l); |
| } |
| |
| public synchronized void removeAppletListener(AppletListener l) { |
| listeners = AppletEventMulticaster.remove(listeners, l); |
| } |
| |
| /** |
| * Dispatch event to the listeners.. |
| */ |
| public void dispatchAppletEvent(int id, Object argument) { |
| //System.out.println("SEND= " + id); |
| if (listeners != null) { |
| AppletEvent evt = new AppletEvent(this, id, argument); |
| listeners.appletStateChanged(evt); |
| } |
| } |
| |
| /** |
| * Send an event. Queue it for execution by the handler thread. |
| */ |
| public void sendEvent(int id) { |
| synchronized(this) { |
| if (queue == null) { |
| //System.out.println("SEND0= " + id); |
| queue = new LinkedBlockingQueue<>(); |
| } |
| boolean inserted = queue.add(id); |
| notifyAll(); |
| } |
| if (id == APPLET_QUIT) { |
| try { |
| joinAppletThread(); // Let the applet event handler exit |
| } catch (InterruptedException e) { |
| } |
| |
| // AppletClassLoader.release() must be called by a Thread |
| // not within the applet's ThreadGroup |
| if (loader == null) |
| loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey()); |
| release(); |
| } |
| } |
| |
| /** |
| * Get an event from the queue. |
| */ |
| synchronized AppletEvent getNextEvent() throws InterruptedException { |
| while (queue == null || queue.isEmpty()) { |
| wait(); |
| } |
| int eventId = queue.take(); |
| return new AppletEvent(this, eventId, null); |
| } |
| |
| boolean emptyEventQueue() { |
| if ((queue == null) || (queue.isEmpty())) |
| return true; |
| else |
| return false; |
| } |
| |
| /** |
| * This kludge is specific to get over AccessControlException thrown during |
| * Applet.stop() or destroy() when static thread is suspended. Set a flag |
| * in AppletClassLoader to indicate that an |
| * AccessControlException for RuntimePermission "modifyThread" or |
| * "modifyThreadGroup" had occurred. |
| */ |
| private void setExceptionStatus(AccessControlException e) { |
| Permission p = e.getPermission(); |
| if (p instanceof RuntimePermission) { |
| if (p.getName().startsWith("modifyThread")) { |
| if (loader == null) |
| loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey()); |
| loader.setExceptionStatus(); |
| } |
| } |
| } |
| |
| /** |
| * Execute applet events. |
| * Here is the state transition diagram |
| * |
| * <pre>{@literal |
| * Note: (XXX) is the action |
| * APPLET_XXX is the state |
| * (applet code loaded) --> APPLET_LOAD -- (applet init called)--> APPLET_INIT -- |
| * (applet start called) --> APPLET_START -- (applet stop called) --> APPLET_STOP -- |
| * (applet destroyed called) --> APPLET_DESTROY --> (applet gets disposed) --> |
| * APPLET_DISPOSE --> ... |
| * }</pre> |
| * |
| * In the legacy lifecycle model. The applet gets loaded, inited and started. So it stays |
| * in the APPLET_START state unless the applet goes away(refresh page or leave the page). |
| * So the applet stop method called and the applet enters APPLET_STOP state. Then if the applet |
| * is revisited, it will call applet start method and enter the APPLET_START state and stay there. |
| * |
| * In the modern lifecycle model. When the applet first time visited, it is same as legacy lifecycle |
| * model. However, when the applet page goes away. It calls applet stop method and enters APPLET_STOP |
| * state and then applet destroyed method gets called and enters APPLET_DESTROY state. |
| * |
| * This code is also called by AppletViewer. In AppletViewer "Restart" menu, the applet is jump from |
| * APPLET_STOP to APPLET_DESTROY and to APPLET_INIT . |
| * |
| * Also, the applet can jump from APPLET_INIT state to APPLET_DESTROY (in Netscape/Mozilla case). |
| * Same as APPLET_LOAD to |
| * APPLET_DISPOSE since all of this are triggered by browser. |
| * |
| */ |
| @Override |
| public void run() { |
| |
| Thread curThread = Thread.currentThread(); |
| if (curThread == loaderThread) { |
| // if we are in the loader thread, cause |
| // loading to occur. We may exit this with |
| // status being APPLET_DISPOSE, APPLET_ERROR, |
| // or APPLET_LOAD |
| runLoader(); |
| return; |
| } |
| |
| boolean disposed = false; |
| while (!disposed && !curThread.isInterrupted()) { |
| AppletEvent evt; |
| try { |
| evt = getNextEvent(); |
| } catch (InterruptedException e) { |
| showAppletStatus("bail"); |
| return; |
| } |
| |
| //showAppletStatus("EVENT = " + evt.getID()); |
| try { |
| switch (evt.getID()) { |
| case APPLET_LOAD: |
| if (!okToLoad()) { |
| break; |
| } |
| // This complexity allows loading of applets to be |
| // interruptable. The actual thread loading runs |
| // in a separate thread, so it can be interrupted |
| // without harming the applet thread. |
| // So that we don't have to worry about |
| // concurrency issues, the main applet thread waits |
| // until the loader thread terminates. |
| // (one way or another). |
| if (loaderThread == null) { |
| // REMIND: do we want a name? |
| //System.out.println("------------------- loading applet"); |
| setLoaderThread(new ManagedLocalsThread(this)); |
| loaderThread.start(); |
| // we get to go to sleep while this runs |
| loaderThread.join(); |
| setLoaderThread(null); |
| } else { |
| // REMIND: issue an error -- this case should never |
| // occur. |
| } |
| break; |
| |
| case APPLET_INIT: |
| // AppletViewer "Restart" will jump from destroy method to |
| // init, that is why we need to check status w/ APPLET_DESTROY |
| if (status != APPLET_LOAD && status != APPLET_DESTROY) { |
| showAppletStatus("notloaded"); |
| break; |
| } |
| applet.resize(defaultAppletSize); |
| |
| if (PerformanceLogger.loggingEnabled()) { |
| PerformanceLogger.setTime("Applet Init"); |
| PerformanceLogger.outputLog(); |
| } |
| applet.init(); |
| |
| //Need the default(fallback) font to be created in this AppContext |
| Font f = getFont(); |
| if (f == null || |
| "dialog".equals(f.getFamily().toLowerCase(Locale.ENGLISH)) && |
| f.getSize() == 12 && f.getStyle() == Font.PLAIN) { |
| setFont(new Font(Font.DIALOG, Font.PLAIN, 12)); |
| } |
| |
| // Validate the applet in event dispatch thread |
| // to avoid deadlock. |
| try { |
| final AppletPanel p = this; |
| Runnable r = new Runnable() { |
| @Override |
| public void run() { |
| p.validate(); |
| } |
| }; |
| AWTAccessor.getEventQueueAccessor().invokeAndWait(applet, r); |
| } |
| catch(InterruptedException ie) { |
| } |
| catch(InvocationTargetException ite) { |
| } |
| |
| status = APPLET_INIT; |
| showAppletStatus("inited"); |
| break; |
| |
| case APPLET_START: |
| { |
| if (status != APPLET_INIT && status != APPLET_STOP) { |
| showAppletStatus("notinited"); |
| break; |
| } |
| applet.resize(currentAppletSize); |
| applet.start(); |
| |
| // Validate and show the applet in event dispatch thread |
| // to avoid deadlock. |
| try { |
| final AppletPanel p = this; |
| final Applet a = applet; |
| Runnable r = new Runnable() { |
| @Override |
| public void run() { |
| p.validate(); |
| a.setVisible(true); |
| |
| // Fix for BugTraq ID 4041703. |
| // Set the default focus for an applet. |
| if (hasInitialFocus()) { |
| setDefaultFocus(); |
| } |
| } |
| }; |
| AWTAccessor.getEventQueueAccessor().invokeAndWait(applet, r); |
| } |
| catch(InterruptedException ie) { |
| } |
| catch(InvocationTargetException ite) { |
| } |
| |
| status = APPLET_START; |
| showAppletStatus("started"); |
| break; |
| } |
| |
| case APPLET_STOP: |
| if (status != APPLET_START) { |
| showAppletStatus("notstarted"); |
| break; |
| } |
| status = APPLET_STOP; |
| |
| // Hide the applet in event dispatch thread |
| // to avoid deadlock. |
| try { |
| final Applet a = applet; |
| Runnable r = new Runnable() { |
| @Override |
| public void run() { |
| a.setVisible(false); |
| } |
| }; |
| AWTAccessor.getEventQueueAccessor().invokeAndWait(applet, r); |
| } |
| catch(InterruptedException ie) { |
| } |
| catch(InvocationTargetException ite) { |
| } |
| |
| |
| // During Applet.stop(), any AccessControlException on an involved Class remains in |
| // the "memory" of the AppletClassLoader. If the same instance of the ClassLoader is |
| // reused, the same exception will occur during class loading. Set the AppletClassLoader's |
| // exceptionStatusSet flag to allow recognition of what had happened |
| // when reusing AppletClassLoader object. |
| try { |
| applet.stop(); |
| } catch (java.security.AccessControlException e) { |
| setExceptionStatus(e); |
| // rethrow exception to be handled as it normally would be. |
| throw e; |
| } |
| showAppletStatus("stopped"); |
| break; |
| |
| case APPLET_DESTROY: |
| if (status != APPLET_STOP && status != APPLET_INIT) { |
| showAppletStatus("notstopped"); |
| break; |
| } |
| status = APPLET_DESTROY; |
| |
| // During Applet.destroy(), any AccessControlException on an involved Class remains in |
| // the "memory" of the AppletClassLoader. If the same instance of the ClassLoader is |
| // reused, the same exception will occur during class loading. Set the AppletClassLoader's |
| // exceptionStatusSet flag to allow recognition of what had happened |
| // when reusing AppletClassLoader object. |
| try { |
| applet.destroy(); |
| } catch (java.security.AccessControlException e) { |
| setExceptionStatus(e); |
| // rethrow exception to be handled as it normally would be. |
| throw e; |
| } |
| showAppletStatus("destroyed"); |
| break; |
| |
| case APPLET_DISPOSE: |
| if (status != APPLET_DESTROY && status != APPLET_LOAD) { |
| showAppletStatus("notdestroyed"); |
| break; |
| } |
| status = APPLET_DISPOSE; |
| |
| try { |
| final Applet a = applet; |
| Runnable r = new Runnable() { |
| @Override |
| public void run() { |
| remove(a); |
| } |
| }; |
| AWTAccessor.getEventQueueAccessor().invokeAndWait(applet, r); |
| } |
| catch(InterruptedException ie) |
| { |
| } |
| catch(InvocationTargetException ite) |
| { |
| } |
| applet = null; |
| showAppletStatus("disposed"); |
| disposed = true; |
| break; |
| |
| case APPLET_QUIT: |
| return; |
| } |
| } catch (Exception e) { |
| status = APPLET_ERROR; |
| if (e.getMessage() != null) { |
| showAppletStatus("exception2", e.getClass().getName(), |
| e.getMessage()); |
| } else { |
| showAppletStatus("exception", e.getClass().getName()); |
| } |
| showAppletException(e); |
| } catch (ThreadDeath e) { |
| showAppletStatus("death"); |
| return; |
| } catch (Error e) { |
| status = APPLET_ERROR; |
| if (e.getMessage() != null) { |
| showAppletStatus("error2", e.getClass().getName(), |
| e.getMessage()); |
| } else { |
| showAppletStatus("error", e.getClass().getName()); |
| } |
| showAppletException(e); |
| } |
| clearLoadAbortRequest(); |
| } |
| } |
| |
| /** |
| * Gets most recent focus owner component associated with the given window. |
| * It does that without calling Window.getMostRecentFocusOwner since it |
| * provides its own logic contradicting with setDefautlFocus. Instead, it |
| * calls KeyboardFocusManager directly. |
| */ |
| private Component getMostRecentFocusOwnerForWindow(Window w) { |
| Method meth = AccessController.doPrivileged( |
| new PrivilegedAction<Method>() { |
| @Override |
| public Method run() { |
| Method meth = null; |
| try { |
| meth = KeyboardFocusManager.class.getDeclaredMethod( |
| "getMostRecentFocusOwner", |
| new Class<?>[]{Window.class}); |
| meth.setAccessible(true); |
| } catch (Exception e) { |
| // Must never happen |
| e.printStackTrace(); |
| } |
| return meth; |
| } |
| }); |
| if (meth != null) { |
| // Meth refers static method |
| try { |
| return (Component)meth.invoke(null, new Object[] {w}); |
| } catch (Exception e) { |
| // Must never happen |
| e.printStackTrace(); |
| } |
| } |
| // Will get here if exception was thrown or meth is null |
| return w.getMostRecentFocusOwner(); |
| } |
| |
| /* |
| * Fix for BugTraq ID 4041703. |
| * Set the focus to a reasonable default for an Applet. |
| */ |
| private void setDefaultFocus() { |
| Component toFocus = null; |
| Container parent = getParent(); |
| |
| if(parent != null) { |
| if (parent instanceof Window) { |
| toFocus = getMostRecentFocusOwnerForWindow((Window)parent); |
| if (toFocus == parent || toFocus == null) { |
| toFocus = parent.getFocusTraversalPolicy(). |
| getInitialComponent((Window)parent); |
| } |
| } else if (parent.isFocusCycleRoot()) { |
| toFocus = parent.getFocusTraversalPolicy(). |
| getDefaultComponent(parent); |
| } |
| } |
| |
| if (toFocus != null) { |
| if (parent instanceof EmbeddedFrame) { |
| ((EmbeddedFrame) parent).synthesizeWindowActivation(true); |
| } |
| // EmbeddedFrame might have focus before the applet was added. |
| // Thus after its activation the most recent focus owner will be |
| // restored. We need the applet's initial focusabled component to |
| // be focused here. |
| toFocus.requestFocusInWindow(); |
| } |
| } |
| |
| /** |
| * Load the applet into memory. |
| * Runs in a seperate (and interruptible) thread from the rest of the |
| * applet event processing so that it can be gracefully interrupted from |
| * things like HotJava. |
| */ |
| @SuppressWarnings("deprecation") |
| private void runLoader() { |
| if (status != APPLET_DISPOSE) { |
| showAppletStatus("notdisposed"); |
| return; |
| } |
| |
| dispatchAppletEvent(APPLET_LOADING, null); |
| |
| // REMIND -- might be cool to visually indicate loading here -- |
| // maybe do animation? |
| status = APPLET_LOAD; |
| |
| // Create a class loader |
| loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey()); |
| |
| // Load the archives if present. |
| // REMIND - this probably should be done in a separate thread, |
| // or at least the additional archives (epll). |
| |
| String code = getCode(); |
| |
| // setup applet AppContext |
| // this must be called before loadJarFiles |
| setupAppletAppContext(); |
| |
| try { |
| loadJarFiles(loader); |
| applet = createApplet(loader); |
| } catch (ClassNotFoundException e) { |
| status = APPLET_ERROR; |
| showAppletStatus("notfound", code); |
| showAppletLog("notfound", code); |
| showAppletException(e); |
| return; |
| } catch (InstantiationException e) { |
| status = APPLET_ERROR; |
| showAppletStatus("nocreate", code); |
| showAppletLog("nocreate", code); |
| showAppletException(e); |
| return; |
| } catch (IllegalAccessException e) { |
| status = APPLET_ERROR; |
| showAppletStatus("noconstruct", code); |
| showAppletLog("noconstruct", code); |
| showAppletException(e); |
| // sbb -- I added a return here |
| return; |
| } catch (Exception e) { |
| status = APPLET_ERROR; |
| showAppletStatus("exception", e.getMessage()); |
| showAppletException(e); |
| return; |
| } catch (ThreadDeath e) { |
| status = APPLET_ERROR; |
| showAppletStatus("death"); |
| return; |
| } catch (Error e) { |
| status = APPLET_ERROR; |
| showAppletStatus("error", e.getMessage()); |
| showAppletException(e); |
| return; |
| } finally { |
| // notify that loading is no longer going on |
| dispatchAppletEvent(APPLET_LOADING_COMPLETED, null); |
| } |
| |
| // Fixed #4508194: NullPointerException thrown during |
| // quick page switch |
| // |
| if (applet != null) |
| { |
| // Stick it in the frame |
| applet.setStub(this); |
| applet.hide(); |
| add("Center", applet); |
| showAppletStatus("loaded"); |
| validate(); |
| } |
| } |
| |
| protected Applet createApplet(final AppletClassLoader loader) throws ClassNotFoundException, |
| IllegalAccessException, IOException, InstantiationException, InterruptedException { |
| String code = getCode(); |
| |
| if (code != null) { |
| applet = (Applet)loader.loadCode(code).newInstance(); |
| } else { |
| String msg = "nocode"; |
| status = APPLET_ERROR; |
| showAppletStatus(msg); |
| showAppletLog(msg); |
| repaint(); |
| } |
| |
| // Determine the JDK level that the applet targets. |
| // This is critical for enabling certain backward |
| // compatibility switch if an applet is a JDK 1.1 |
| // applet. [stanley.ho] |
| findAppletJDKLevel(applet); |
| |
| if (Thread.interrupted()) { |
| try { |
| status = APPLET_DISPOSE; // APPLET_ERROR? |
| applet = null; |
| // REMIND: This may not be exactly the right thing: the |
| // status is set by the stop button and not necessarily |
| // here. |
| showAppletStatus("death"); |
| } finally { |
| Thread.currentThread().interrupt(); // resignal interrupt |
| } |
| return null; |
| } |
| return applet; |
| } |
| |
| protected void loadJarFiles(AppletClassLoader loader) throws IOException, |
| InterruptedException { |
| // Load the archives if present. |
| // REMIND - this probably should be done in a separate thread, |
| // or at least the additional archives (epll). |
| String jarFiles = getJarFiles(); |
| |
| if (jarFiles != null) { |
| StringTokenizer st = new StringTokenizer(jarFiles, ",", false); |
| while(st.hasMoreTokens()) { |
| String tok = st.nextToken().trim(); |
| try { |
| loader.addJar(tok); |
| } catch (IllegalArgumentException e) { |
| // bad archive name |
| continue; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Request that the loading of the applet be stopped. |
| */ |
| protected synchronized void stopLoading() { |
| // REMIND: fill in the body |
| if (loaderThread != null) { |
| //System.out.println("Interrupting applet loader thread: " + loaderThread); |
| loaderThread.interrupt(); |
| } else { |
| setLoadAbortRequest(); |
| } |
| } |
| |
| |
| protected synchronized boolean okToLoad() { |
| return !loadAbortRequest; |
| } |
| |
| protected synchronized void clearLoadAbortRequest() { |
| loadAbortRequest = false; |
| } |
| |
| protected synchronized void setLoadAbortRequest() { |
| loadAbortRequest = true; |
| } |
| |
| |
| private synchronized void setLoaderThread(Thread loaderThread) { |
| this.loaderThread = loaderThread; |
| } |
| |
| /** |
| * Return true when the applet has been started. |
| */ |
| @Override |
| public boolean isActive() { |
| return status == APPLET_START; |
| } |
| |
| |
| private EventQueue appEvtQ = null; |
| /** |
| * Is called when the applet wants to be resized. |
| */ |
| @Override |
| public void appletResize(int width, int height) { |
| currentAppletSize.width = width; |
| currentAppletSize.height = height; |
| final Dimension currentSize = new Dimension(currentAppletSize.width, |
| currentAppletSize.height); |
| |
| if(loader != null) { |
| AppContext appCtxt = loader.getAppContext(); |
| if(appCtxt != null) |
| appEvtQ = (java.awt.EventQueue)appCtxt.get(AppContext.EVENT_QUEUE_KEY); |
| } |
| |
| final AppletPanel ap = this; |
| if (appEvtQ != null){ |
| appEvtQ.postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(), |
| new Runnable() { |
| @Override |
| public void run() { |
| if (ap != null) { |
| ap.dispatchAppletEvent( |
| APPLET_RESIZE, |
| currentSize); |
| } |
| } |
| })); |
| } |
| } |
| |
| @Override |
| public void setBounds(int x, int y, int width, int height) { |
| super.setBounds(x, y, width, height); |
| currentAppletSize.width = width; |
| currentAppletSize.height = height; |
| } |
| |
| public Applet getApplet() { |
| return applet; |
| } |
| |
| /** |
| * Status line. Called by the AppletPanel to provide |
| * feedback on the Applet's state. |
| */ |
| protected void showAppletStatus(String status) { |
| getAppletContext().showStatus(amh.getMessage(status)); |
| } |
| |
| protected void showAppletStatus(String status, Object arg) { |
| getAppletContext().showStatus(amh.getMessage(status, arg)); |
| } |
| protected void showAppletStatus(String status, Object arg1, Object arg2) { |
| getAppletContext().showStatus(amh.getMessage(status, arg1, arg2)); |
| } |
| |
| /** |
| * Called by the AppletPanel to print to the log. |
| */ |
| protected void showAppletLog(String msg) { |
| System.out.println(amh.getMessage(msg)); |
| } |
| |
| protected void showAppletLog(String msg, Object arg) { |
| System.out.println(amh.getMessage(msg, arg)); |
| } |
| |
| /** |
| * Called by the AppletPanel to provide |
| * feedback when an exception has happened. |
| */ |
| protected void showAppletException(Throwable t) { |
| t.printStackTrace(); |
| repaint(); |
| } |
| |
| /** |
| * Get caching key for classloader cache |
| */ |
| public String getClassLoaderCacheKey() |
| { |
| /** |
| * Fixed #4501142: Classloader sharing policy doesn't |
| * take "archive" into account. This will be overridden |
| * by Java Plug-in. [stanleyh] |
| */ |
| return getCodeBase().toString(); |
| } |
| |
| /** |
| * The class loaders |
| */ |
| private static HashMap<String, AppletClassLoader> classloaders = new HashMap<>(); |
| |
| /** |
| * Flush a class loader. |
| */ |
| public static synchronized void flushClassLoader(String key) { |
| classloaders.remove(key); |
| } |
| |
| /** |
| * Flush all class loaders. |
| */ |
| public static synchronized void flushClassLoaders() { |
| classloaders = new HashMap<>(); |
| } |
| |
| /** |
| * This method actually creates an AppletClassLoader. |
| * |
| * It can be override by subclasses (such as the Plug-in) |
| * to provide different classloaders. |
| */ |
| protected AppletClassLoader createClassLoader(final URL codebase) { |
| return new AppletClassLoader(codebase); |
| } |
| |
| /** |
| * Get a class loader. Create in a restricted context |
| */ |
| synchronized AppletClassLoader getClassLoader(final URL codebase, final String key) { |
| AppletClassLoader c = classloaders.get(key); |
| if (c == null) { |
| AccessControlContext acc = |
| getAccessControlContext(codebase); |
| c = AccessController.doPrivileged( |
| new PrivilegedAction<AppletClassLoader>() { |
| @Override |
| public AppletClassLoader run() { |
| AppletClassLoader ac = createClassLoader(codebase); |
| /* Should the creation of the classloader be |
| * within the class synchronized block? Since |
| * this class is used by the plugin, take care |
| * to avoid deadlocks, or specialize |
| * AppletPanel within the plugin. It may take |
| * an arbitrary amount of time to create a |
| * class loader (involving getting Jar files |
| * etc.) and may block unrelated applets from |
| * finishing createAppletThread (due to the |
| * class synchronization). If |
| * createAppletThread does not finish quickly, |
| * the applet cannot process other messages, |
| * particularly messages such as destroy |
| * (which timeout when called from the browser). |
| */ |
| synchronized (getClass()) { |
| AppletClassLoader res = classloaders.get(key); |
| if (res == null) { |
| classloaders.put(key, ac); |
| return ac; |
| } else { |
| return res; |
| } |
| } |
| } |
| },acc); |
| } |
| return c; |
| } |
| |
| /** |
| * get the context for the AppletClassLoader we are creating. |
| * the context is granted permission to create the class loader, |
| * connnect to the codebase, and whatever else the policy grants |
| * to all codebases. |
| */ |
| private AccessControlContext getAccessControlContext(final URL codebase) { |
| |
| PermissionCollection perms = AccessController.doPrivileged( |
| new PrivilegedAction<PermissionCollection>() { |
| @Override |
| public PermissionCollection run() { |
| Policy p = java.security.Policy.getPolicy(); |
| if (p != null) { |
| return p.getPermissions(new CodeSource(null, |
| (java.security.cert.Certificate[]) null)); |
| } else { |
| return null; |
| } |
| } |
| }); |
| |
| if (perms == null) |
| perms = new Permissions(); |
| |
| //XXX: this is needed to be able to create the classloader itself! |
| |
| perms.add(SecurityConstants.CREATE_CLASSLOADER_PERMISSION); |
| |
| Permission p; |
| java.net.URLConnection urlConnection = null; |
| try { |
| urlConnection = codebase.openConnection(); |
| p = urlConnection.getPermission(); |
| } catch (java.io.IOException ioe) { |
| p = null; |
| } |
| |
| if (p != null) |
| perms.add(p); |
| |
| if (p instanceof FilePermission) { |
| |
| String path = p.getName(); |
| |
| int endIndex = path.lastIndexOf(File.separatorChar); |
| |
| if (endIndex != -1) { |
| path = path.substring(0, endIndex+1); |
| |
| if (path.endsWith(File.separator)) { |
| path += "-"; |
| } |
| perms.add(new FilePermission(path, |
| SecurityConstants.FILE_READ_ACTION)); |
| } |
| } else { |
| URL locUrl = codebase; |
| if (urlConnection instanceof JarURLConnection) { |
| locUrl = ((JarURLConnection)urlConnection).getJarFileURL(); |
| } |
| String host = locUrl.getHost(); |
| if (host != null && (host.length() > 0)) |
| perms.add(new SocketPermission(host, |
| SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION)); |
| } |
| |
| ProtectionDomain domain = |
| new ProtectionDomain(new CodeSource(codebase, |
| (java.security.cert.Certificate[]) null), perms); |
| AccessControlContext acc = |
| new AccessControlContext(new ProtectionDomain[] { domain }); |
| |
| return acc; |
| } |
| |
| public Thread getAppletHandlerThread() { |
| return handler; |
| } |
| |
| public int getAppletWidth() { |
| return currentAppletSize.width; |
| } |
| |
| public int getAppletHeight() { |
| return currentAppletSize.height; |
| } |
| |
| public static void changeFrameAppContext(Frame frame, AppContext newAppContext) |
| { |
| // Fixed #4754451: Applet can have methods running on main |
| // thread event queue. |
| // |
| // The cause of this bug is that the frame of the applet |
| // is created in main thread group. Thus, when certain |
| // AWT/Swing events are generated, the events will be |
| // dispatched through the wrong event dispatch thread. |
| // |
| // To fix this, we rearrange the AppContext with the frame, |
| // so the proper event queue will be looked up. |
| // |
| // Swing also maintains a Frame list for the AppContext, |
| // so we will have to rearrange it as well. |
| |
| // Check if frame's AppContext has already been set properly |
| AppContext oldAppContext = SunToolkit.targetToAppContext(frame); |
| |
| if (oldAppContext == newAppContext) |
| return; |
| |
| // Synchronization on Window.class is needed for locking the |
| // critical section of the window list in AppContext. |
| synchronized (Window.class) |
| { |
| WeakReference<Window> weakRef = null; |
| // Remove frame from the Window list in wrong AppContext |
| { |
| // Lookup current frame's AppContext |
| @SuppressWarnings("unchecked") |
| Vector<WeakReference<Window>> windowList = |
| (Vector<WeakReference<Window>>)oldAppContext.get(Window.class); |
| if (windowList != null) { |
| for (WeakReference<Window> ref : windowList) { |
| if (ref.get() == frame) { |
| weakRef = ref; |
| break; |
| } |
| } |
| // Remove frame from wrong AppContext |
| if (weakRef != null) |
| windowList.remove(weakRef); |
| } |
| } |
| |
| // Put the frame into the applet's AppContext map |
| SunToolkit.insertTargetMapping(frame, newAppContext); |
| |
| // Insert frame into the Window list in the applet's AppContext map |
| { |
| @SuppressWarnings("unchecked") |
| Vector<WeakReference<Window>> windowList = |
| (Vector<WeakReference<Window>>)newAppContext.get(Window.class); |
| if (windowList == null) { |
| windowList = new Vector<WeakReference<Window>>(); |
| newAppContext.put(Window.class, windowList); |
| } |
| // use the same weakRef here as it is used elsewhere |
| windowList.add(weakRef); |
| } |
| } |
| } |
| |
| // Flag to indicate if applet is targeted for JDK 1.1. |
| private boolean jdk11Applet = false; |
| |
| // Flag to indicate if applet is targeted for JDK 1.2. |
| private boolean jdk12Applet = false; |
| |
| /** |
| * Determine JDK level of an applet. |
| */ |
| private void findAppletJDKLevel(Applet applet) |
| { |
| // To determine the JDK level of an applet, the |
| // most reliable way is to check the major version |
| // of the applet class file. |
| |
| // synchronized on applet class object, so calling from |
| // different instances of the same applet will be |
| // serialized. |
| Class<?> appletClass = applet.getClass(); |
| |
| synchronized(appletClass) { |
| // Determine if the JDK level of an applet has been |
| // checked before. |
| Boolean jdk11Target = loader.isJDK11Target(appletClass); |
| Boolean jdk12Target = loader.isJDK12Target(appletClass); |
| |
| // if applet JDK level has been checked before, retrieve |
| // value and return. |
| if (jdk11Target != null || jdk12Target != null) { |
| jdk11Applet = (jdk11Target == null) ? false : jdk11Target.booleanValue(); |
| jdk12Applet = (jdk12Target == null) ? false : jdk12Target.booleanValue(); |
| return; |
| } |
| |
| String name = appletClass.getName(); |
| |
| // first convert any '.' to '/' |
| name = name.replace('.', '/'); |
| |
| // append .class |
| final String resourceName = name + ".class"; |
| |
| byte[] classHeader = new byte[8]; |
| |
| try (InputStream is = AccessController.doPrivileged( |
| (PrivilegedAction<InputStream>) () -> loader.getResourceAsStream(resourceName))) { |
| |
| // Read the first 8 bytes of the class file |
| int byteRead = is.read(classHeader, 0, 8); |
| |
| // return if the header is not read in entirely |
| // for some reasons. |
| if (byteRead != 8) |
| return; |
| } |
| catch (IOException e) { |
| return; |
| } |
| |
| // Check major version in class file header |
| int major_version = readShort(classHeader, 6); |
| |
| // Major version in class file is as follows: |
| // 45 - JDK 1.1 |
| // 46 - JDK 1.2 |
| // 47 - JDK 1.3 |
| // 48 - JDK 1.4 |
| // 49 - JDK 1.5 |
| if (major_version < 46) |
| jdk11Applet = true; |
| else if (major_version == 46) |
| jdk12Applet = true; |
| |
| // Store applet JDK level in AppContext for later lookup, |
| // e.g. page switch. |
| loader.setJDK11Target(appletClass, jdk11Applet); |
| loader.setJDK12Target(appletClass, jdk12Applet); |
| } |
| } |
| |
| /** |
| * Return true if applet is targeted to JDK 1.1. |
| */ |
| protected boolean isJDK11Applet() { |
| return jdk11Applet; |
| } |
| |
| /** |
| * Return true if applet is targeted to JDK1.2. |
| */ |
| protected boolean isJDK12Applet() { |
| return jdk12Applet; |
| } |
| |
| /** |
| * Read short from byte array. |
| */ |
| private int readShort(byte[] b, int off) { |
| int hi = readByte(b[off]); |
| int lo = readByte(b[off + 1]); |
| return (hi << 8) | lo; |
| } |
| |
| private int readByte(byte b) { |
| return ((int)b) & 0xFF; |
| } |
| |
| |
| private static AppletMessageHandler amh = new AppletMessageHandler("appletpanel"); |
| } |