1318 lines
46 KiB
Java
1318 lines
46 KiB
Java
/*
|
|
* 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 sun.awt.AWTAccessor;
|
|
import sun.awt.AppContext;
|
|
import sun.awt.EmbeddedFrame;
|
|
import sun.awt.SunToolkit;
|
|
import sun.misc.MessageUtils;
|
|
import sun.misc.PerformanceLogger;
|
|
import sun.misc.Queue;
|
|
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
|
|
*/
|
|
public
|
|
abstract class AppletPanel extends Panel implements AppletStub, Runnable {
|
|
|
|
/**
|
|
* The applet (if loaded).
|
|
*/
|
|
Applet applet;
|
|
|
|
/**
|
|
* Applet will allow initialization. Should be
|
|
* set to false if loading a serialized applet
|
|
* that was pickled in the init=true state.
|
|
*/
|
|
protected boolean doInit = true;
|
|
|
|
|
|
/**
|
|
* The classloader for the applet.
|
|
*/
|
|
protected AppletClassLoader loader;
|
|
|
|
/* applet event ids */
|
|
public final static int APPLET_DISPOSE = 0;
|
|
public final static int APPLET_LOAD = 1;
|
|
public final static int APPLET_INIT = 2;
|
|
public final static int APPLET_START = 3;
|
|
public final static int APPLET_STOP = 4;
|
|
public final static int APPLET_DESTROY = 5;
|
|
public final static int APPLET_QUIT = 6;
|
|
public final static int APPLET_ERROR = 7;
|
|
|
|
/* send to the parent to force relayout */
|
|
public final static int APPLET_RESIZE = 51234;
|
|
|
|
/* sent to a (distant) parent to indicate that the applet is being
|
|
* loaded or as completed loading
|
|
*/
|
|
public final static int APPLET_LOADING = 51235;
|
|
public final static 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);
|
|
|
|
MessageUtils mu = new MessageUtils();
|
|
|
|
/**
|
|
* The thread to use during applet loading
|
|
*/
|
|
|
|
Thread loaderThread = null;
|
|
|
|
/**
|
|
* Flag to indicate that a loading has been cancelled
|
|
*/
|
|
boolean loadAbortRequest = false;
|
|
|
|
/* abstract classes */
|
|
abstract protected String getCode();
|
|
abstract protected String getJarFiles();
|
|
abstract protected String getSerializedObject();
|
|
|
|
@Override
|
|
abstract public int getWidth();
|
|
@Override
|
|
abstract public int getHeight();
|
|
abstract public 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 Thread(appletGroup, this, "thread " + nm);
|
|
// set the context class loader for this thread
|
|
AccessController.doPrivileged(new PrivilegedAction() {
|
|
@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
|
|
public Dimension minimumSize() {
|
|
return new Dimension(defaultAppletSize.width,
|
|
defaultAppletSize.height);
|
|
}
|
|
|
|
/**
|
|
* Preferred size
|
|
*/
|
|
@Override
|
|
public Dimension preferredSize() {
|
|
return new Dimension(currentAppletSize.width,
|
|
currentAppletSize.height);
|
|
}
|
|
|
|
private AppletListener listeners;
|
|
|
|
/**
|
|
* AppletEvent Queue
|
|
*/
|
|
private Queue queue = null;
|
|
|
|
|
|
synchronized public void addAppletListener(AppletListener l) {
|
|
listeners = AppletEventMulticaster.add(listeners, l);
|
|
}
|
|
|
|
synchronized public 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 Queue();
|
|
}
|
|
Integer eventId = Integer.valueOf(id);
|
|
queue.enqueue(eventId);
|
|
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();
|
|
}
|
|
Integer eventId = (Integer)queue.dequeue();
|
|
return new AppletEvent(this, eventId.intValue(), 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
|
|
*
|
|
* 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 -->....
|
|
*
|
|
* 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 Thread(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 (doInit) {
|
|
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));
|
|
}
|
|
|
|
doInit = true; // allow restarts
|
|
|
|
// 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 = (Method)AccessController.doPrivileged(new PrivilegedAction() {
|
|
@Override
|
|
public Object 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.
|
|
*/
|
|
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 {
|
|
final String serName = getSerializedObject();
|
|
String code = getCode();
|
|
|
|
if (code != null && serName != null) {
|
|
System.err.println(amh.getMessage("runloader.err"));
|
|
// return null;
|
|
throw new InstantiationException("Either \"code\" or \"object\" should be specified, but not both.");
|
|
}
|
|
if (code == null && serName == null) {
|
|
String msg = "nocode";
|
|
status = APPLET_ERROR;
|
|
showAppletStatus(msg);
|
|
showAppletLog(msg);
|
|
repaint();
|
|
}
|
|
if (code != null) {
|
|
applet = (Applet)loader.loadCode(code).newInstance();
|
|
doInit = true;
|
|
} else {
|
|
// serName is not null;
|
|
try (InputStream is = AccessController.doPrivileged(
|
|
(PrivilegedAction<InputStream>)() -> loader.getResourceAsStream(serName));
|
|
ObjectInputStream ois = new AppletObjectInputStream(is, loader)) {
|
|
|
|
applet = (Applet) ois.readObject();
|
|
doInit = false; // skip over the first init
|
|
}
|
|
}
|
|
|
|
// 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 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 = (AppletClassLoader)classloaders.get(key);
|
|
if (c == null) {
|
|
AccessControlContext acc =
|
|
getAccessControlContext(codebase);
|
|
c = (AppletClassLoader)
|
|
AccessController.doPrivileged(new PrivilegedAction() {
|
|
@Override
|
|
public Object 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 =
|
|
(AppletClassLoader)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 = (PermissionCollection)
|
|
AccessController.doPrivileged(new PrivilegedAction() {
|
|
@Override
|
|
public Object 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 weakRef = null;
|
|
// Remove frame from the Window list in wrong AppContext
|
|
{
|
|
// Lookup current frame's AppContext
|
|
Vector<WeakReference<Window>> windowList = (Vector<WeakReference<Window>>)oldAppContext.get(Window.class);
|
|
if (windowList != null) {
|
|
for (WeakReference 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
|
|
{
|
|
Vector<WeakReference<Window>> windowList = (Vector)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 = (Boolean) loader.isJDK11Target(appletClass);
|
|
Boolean jdk12Target = (Boolean) 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");
|
|
}
|