1802 lines
53 KiB
Java
1802 lines
53 KiB
Java
import java.security.*;
|
|
import java.applet.Applet;
|
|
import java.awt.*;
|
|
import java.util.*;
|
|
import java.util.concurrent.*;
|
|
import java.awt.event.*;
|
|
import netscape.javascript.*;
|
|
import java.io.*;
|
|
import java.lang.reflect.*;
|
|
import java.net.URL;
|
|
import java.awt.datatransfer.*;
|
|
import javax.swing.JOptionPane;
|
|
import javax.swing.JDialog;
|
|
import java.awt.image.*;
|
|
|
|
public final class DOHRobot extends Applet{
|
|
// order of execution:
|
|
// wait for user to trust applet
|
|
// load security manager to prevent Safari hang
|
|
// discover document root in screen coordinates
|
|
// discover keyboard capabilities
|
|
// tell doh to continue with the test
|
|
|
|
// link to doh
|
|
// To invoke doh, call eval with window.eval("jsexp")
|
|
// Note that the "window" is an iframe!
|
|
// You might need to break out of the iframe with an intermediate function
|
|
// in the parent window.
|
|
private JSObject window = null;
|
|
|
|
// java.awt.Robot
|
|
// drives the test
|
|
// you need to sign the applet JAR for this to work
|
|
private Robot robot = null;
|
|
|
|
// In order to preserve the execution order of Robot commands,
|
|
// we have to serialize commands by having them join() the previous one.
|
|
// Otherwise, if you run doh.robot.typeKeys("dijit"), you frequently get something
|
|
// like "diijt"
|
|
//private static Thread previousThread = null;
|
|
|
|
private static ExecutorService threadPool = null;
|
|
|
|
// Keyboard discovery.
|
|
// At init, the Robot types keys into a textbox and JavaScript tells the
|
|
// Robot what it got back.
|
|
// charMap maps characters to the KeyEvent that generates the character on
|
|
// the user's machine.
|
|
// charMap uses the Java 1.4.2 (lack of) template syntax for wider
|
|
// compatibility.
|
|
private static HashMap charMap = null;
|
|
// Java key constants to iterate over
|
|
// not all are available on all machines!
|
|
private Vector vkKeys = null;
|
|
// some state variables
|
|
private boolean shift = false;
|
|
private boolean altgraph = false;
|
|
private boolean ctrl = false;
|
|
private boolean alt = false;
|
|
private boolean meta = false;
|
|
private boolean numlockDisabled = false;
|
|
private long timingError = 0; // how much time the last robot call was off by
|
|
// shake hands with JavaScript the first keypess to wake up FF2/Mac
|
|
private boolean jsready = false;
|
|
private String keystring = "";
|
|
|
|
// Firebug gets a little too curious about our applet for its own good
|
|
// setting firebugIgnore to true ensures Firebug doesn't break the applet
|
|
public boolean firebugIgnore = true;
|
|
|
|
private static String os=System.getProperty("os.name").toUpperCase();
|
|
private static Toolkit toolkit=Toolkit.getDefaultToolkit();
|
|
|
|
private SecurityManager securitymanager;
|
|
private double key = -1;
|
|
|
|
// The screen x,y of the document upper left corner.
|
|
// We only set it once so people are less likely to take it over.
|
|
private boolean inited = false;
|
|
private int docScreenX = -100;
|
|
private int docScreenY = -100;
|
|
private int docScreenXMax;
|
|
private int docScreenYMax;
|
|
private Point margin = null;
|
|
private boolean mouseSecurity = false;
|
|
private int dohscreen = -1;
|
|
|
|
// The last reported mouse x,y.
|
|
// If this is different from the real one, something's up.
|
|
private int lastMouseX;
|
|
private int lastMouseY;
|
|
public int dir=1;
|
|
|
|
// save a pointer to doh.robot for fast access
|
|
JSObject dohrobot = null;
|
|
|
|
// trackingImage to visually track robot down
|
|
private BufferedImage trackingImage;
|
|
Point locationOnScreen = null;
|
|
|
|
// java.awt.Applet methods
|
|
public void stop(){
|
|
window = null;
|
|
dohrobot = null;
|
|
// only secure code run once
|
|
if(key != -2){
|
|
// prevent further execution of secure functions
|
|
key = -2;
|
|
// Java calls this when you close the window.
|
|
// It plays nice and restores the old security manager.
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
if(threadPool!=null){
|
|
threadPool.shutdownNow();
|
|
}
|
|
log("Stop");
|
|
securitymanager.checkTopLevelWindow(null);
|
|
log("Security manager reset");
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
final private class onvisible extends ComponentAdapter{
|
|
public void componentShown(ComponentEvent evt){
|
|
// sets the security manager to fix a bug in liveconnect in Safari on Mac
|
|
if(key != -1){ return; }
|
|
Runnable thread = new Runnable(){
|
|
public void run(){
|
|
log("Document root: ~"+applet().getLocationOnScreen().toString());
|
|
while(true){
|
|
try{
|
|
window = (JSObject) JSObject.getWindow(applet());
|
|
break;
|
|
}catch(Exception e){
|
|
// wait
|
|
}
|
|
}
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
log("> init Robot");
|
|
try{
|
|
SecurityManager oldsecurity = System.getSecurityManager();
|
|
boolean needsSecurityManager = applet().getParameter("needsSecurityManager").equals("true");
|
|
log("Socket connections managed? "+needsSecurityManager);
|
|
try{
|
|
securitymanager = oldsecurity;
|
|
securitymanager.checkTopLevelWindow(null);
|
|
// xdomain
|
|
if(charMap == null){
|
|
if(!confirm("DOH has detected that the current Web page is attempting to access DOH,\n"+
|
|
"but belongs to a different domain than the one you agreed to let DOH automate.\n"+
|
|
"If you did not intend to start a new DOH test by visiting this Web page,\n"+
|
|
"press Cancel now and leave the Web page.\n"+
|
|
"Otherwise, press OK to trust this domain to automate DOH tests.")){
|
|
stop();
|
|
return null;
|
|
}
|
|
}
|
|
log("Found old security manager");
|
|
}catch(Exception e){
|
|
log("Making new security manager");
|
|
securitymanager = new RobotSecurityManager(needsSecurityManager,
|
|
oldsecurity);
|
|
securitymanager.checkTopLevelWindow(null);
|
|
System.setSecurityManager(securitymanager);
|
|
}
|
|
}catch(SecurityException e){
|
|
// OpenJDK is very strict; fail gracefully
|
|
}catch(Exception e){
|
|
log("Error calling _init_: "+e.getMessage());
|
|
key = -2;
|
|
e.printStackTrace();
|
|
}
|
|
log("< init Robot");
|
|
return null;
|
|
}
|
|
});
|
|
if(key == -2){
|
|
// applet not trusted
|
|
// start the test without it
|
|
window.eval("doh.robot._appletDead=true;doh.run();");
|
|
}else{
|
|
// now that the applet has really started, let doh know it's ok to use it
|
|
log("_initRobot");
|
|
try{
|
|
dohrobot = (JSObject) window.eval("doh.robot");
|
|
dohrobot.call("_initRobot", new Object[]{ applet() });
|
|
}catch(Exception e){
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
threadPool.execute(thread);
|
|
}
|
|
}
|
|
|
|
public void init(){
|
|
threadPool = Executors.newFixedThreadPool(1);
|
|
// ensure isShowing = true
|
|
addComponentListener(new onvisible());
|
|
ProfilingThread jitProfile=new ProfilingThread ();
|
|
jitProfile.startProfiling();
|
|
jitProfile.endProfiling();
|
|
trackingImage=new BufferedImage(3,3,BufferedImage.TYPE_INT_RGB);
|
|
trackingImage.setRGB(0, 0, 3, 3, new int[]{new Color(255,174,201).getRGB(),new Color(255,127,39).getRGB(),new Color(0,0,0).getRGB(),new Color(237,28,36).getRGB(),new Color(63,72,204).getRGB(),new Color(34,177,76).getRGB(),new Color(181,230,29).getRGB(),new Color(255,255,255).getRGB(),new Color(200,191,231).getRGB()}, 0, 3);
|
|
}
|
|
|
|
// loading functions
|
|
public void _setKey(double key){
|
|
if(key == -1){
|
|
return;
|
|
}else if(this.key == -1){
|
|
this.key = key;
|
|
}
|
|
}
|
|
|
|
protected Point getDesktopMousePosition() throws Exception{
|
|
Class mouseInfoClass;
|
|
Class pointerInfoClass;
|
|
mouseInfoClass = Class.forName("java.awt.MouseInfo");
|
|
pointerInfoClass = Class.forName("java.awt.PointerInfo");
|
|
Method getPointerInfo = mouseInfoClass.getMethod("getPointerInfo", new Class[0]);
|
|
Method getLocation = pointerInfoClass.getMethod("getLocation", new Class[0]);
|
|
Object pointer=null;
|
|
Point p=null;
|
|
try{
|
|
pointer = getPointerInfo.invoke(pointerInfoClass,new Object[0]);
|
|
p = (Point)(getLocation.invoke(pointer,new Object[0]));
|
|
}catch(java.lang.reflect.InvocationTargetException e){
|
|
e.getTargetException().printStackTrace();
|
|
}
|
|
return p;
|
|
}
|
|
|
|
public Point getLocationOnScreen(){
|
|
return locationOnScreen==null? super.getLocationOnScreen(): locationOnScreen;
|
|
}
|
|
|
|
private boolean mouseSecure() throws Exception{
|
|
// Use MouseInfo to ensure that mouse is inside browser.
|
|
// Only works in Java 1.5, but the DOHRobot must compile for 1.4.
|
|
if(!mouseSecurity){ return true; }
|
|
Point mousePosition=null;
|
|
try{
|
|
mousePosition=getDesktopMousePosition();
|
|
log("Mouse position: "+mousePosition);
|
|
}catch(Exception e){
|
|
return true;
|
|
}
|
|
return mousePosition.x >= docScreenX
|
|
&& mousePosition.x <= docScreenXMax
|
|
&& mousePosition.y >= docScreenY
|
|
&& mousePosition.y <= docScreenYMax;
|
|
}
|
|
|
|
private boolean isSecure(double key){
|
|
// make sure key is not unset (-1) or error state (-2) and is the expected key
|
|
boolean result = this.key != -1 && this.key != -2 && this.key == key;
|
|
try{
|
|
// also make sure mouse in good spot
|
|
result=result&&mouseSecure();
|
|
}catch(Exception e){
|
|
e.printStackTrace();
|
|
result=false;
|
|
}
|
|
if(!result&&this.key!=-2){
|
|
this.key=-2;
|
|
window.eval("doh.robot._appletDead=true;");
|
|
log("User aborted test; mouse moved off of browser");
|
|
alert("User aborted test; mouse moved off of browser.");
|
|
log("Key secure: false; mouse in bad spot?");
|
|
}else{
|
|
log("Key secure: " + result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public void _callLoaded(final double sec){
|
|
log("> _callLoaded Robot");
|
|
Runnable thread = new Runnable(){
|
|
public void run(){
|
|
if(!isSecure(sec)){
|
|
return;
|
|
}
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
Point p = getLocationOnScreen();
|
|
if(os.indexOf("MAC") != -1){
|
|
// Work around stupid Apple OS X bug affecting Safari 5.1 and FF4.
|
|
// Seems to have to do with the plugin they compile with rather than the jvm itself because Safari5.0 and FF3.6 still work.
|
|
p = new Point();
|
|
int screen=0;
|
|
dohscreen=-1;
|
|
int mindifference=Integer.MAX_VALUE;
|
|
GraphicsDevice[] screens=GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
|
|
try{
|
|
for(screen=0; screen<screens.length; screen++){
|
|
// get origin of screen in Java virtual coordinates
|
|
Rectangle bounds=screens[screen].getDefaultConfiguration().getBounds();
|
|
// take picture
|
|
DisplayMode mode=screens[screen].getDisplayMode();
|
|
int width=mode.getWidth();
|
|
int height=mode.getHeight();
|
|
int twidth=trackingImage.getWidth();
|
|
int theight=trackingImage.getHeight();
|
|
Robot screenshooter=new Robot(screens[screen]);
|
|
log("screen dimensions: "+width+" "+height);
|
|
BufferedImage screenshot=screenshooter.createScreenCapture(new Rectangle(0,0,width,height));
|
|
// Ideally (in Windows) we would now slide trackingImage until we find an identical match inside screenshot.
|
|
// Unfortunately because the Mac (what we are trying to fix) does terrible, awful things to graphics it displays,
|
|
// we will need to find the "most similar" (smallest difference in pixels) square and click there.
|
|
int x=0,y=0;
|
|
for(x=0; x<=width-twidth; x++){
|
|
for(y=0; y<=height-theight; y++){
|
|
int count=0;
|
|
int difference=0;
|
|
scanImage:
|
|
for(int x2=0; x2<twidth; x2++){
|
|
for(int y2=0; y2<theight; y2++){
|
|
int rgbdiff=Math.abs(screenshot.getRGB(x+x2,y+y2)-trackingImage.getRGB(x2,y2));
|
|
difference=difference+rgbdiff;
|
|
// short circuit mismatches
|
|
if(difference>=mindifference){
|
|
break scanImage;
|
|
}
|
|
}
|
|
}
|
|
if(difference<mindifference){
|
|
// convert device coordinates to virtual coordinates by adding screen's origin
|
|
p.x=x+(int)bounds.getX();
|
|
p.y=y+(int)bounds.getY();
|
|
mindifference=difference;
|
|
dohscreen=screen;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// create temp robot to put mouse in right spot
|
|
robot=new Robot(screens[dohscreen]);
|
|
robot.setAutoWaitForIdle(true);
|
|
}catch(Exception e){
|
|
e.printStackTrace();
|
|
}
|
|
if(p.x==0&&p.y==0){
|
|
// shouldn't happen...
|
|
throw new RuntimeException("Robot not found on screen");
|
|
}
|
|
locationOnScreen=p;
|
|
}else{
|
|
// create default temp robot that should work on non-Macs
|
|
try{
|
|
robot=new Robot();
|
|
robot.setAutoWaitForIdle(true);
|
|
}catch(Exception e){}
|
|
}
|
|
log("Document root: ~"+p.toString());
|
|
int x = p.x + 16;
|
|
int y = p.y + 8;
|
|
// click the mouse over the text box
|
|
try{
|
|
Thread.sleep(100);
|
|
}catch(Exception e){};
|
|
|
|
try{
|
|
// mouse in right spot; restore control to original robot using browser's preferred coordinates
|
|
robot = new Robot();
|
|
robot.setAutoWaitForIdle(true);
|
|
robot.mouseMove(x, y);
|
|
Thread.sleep(100);
|
|
// click 50 times then abort
|
|
int i=0;
|
|
for(i=0; i<50&&!inited; i++){
|
|
robot.mousePress(InputEvent.BUTTON1_MASK);
|
|
Thread.sleep(100);
|
|
robot.mouseRelease(InputEvent.BUTTON1_MASK);
|
|
Thread.sleep(100);
|
|
log("mouse clicked");
|
|
}
|
|
if(i==50){
|
|
applet().stop();
|
|
}
|
|
}catch(Exception e){ e.printStackTrace(); }
|
|
log("< _callLoaded Robot");
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
threadPool.execute(thread);
|
|
}
|
|
|
|
// convenience functions
|
|
private DOHRobot applet(){
|
|
return this;
|
|
}
|
|
|
|
public void log(final String s){
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
System.out.println((new Date()).toString() + ": " + s);
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
private void alert(final String s){
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
window.eval("top.alert(\"" + s + "\");");
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
private boolean confirm(final String s){
|
|
// show a Java confirm dialog.
|
|
// Mac seems to lock up when showing a JS confirm from Java.
|
|
//return JOptionPane.showConfirmDialog(this, s, "doh.robot", JOptionPane.OK_CANCEL_OPTION)==JOptionPane.OK_OPTION);
|
|
JOptionPane pane = new JOptionPane(s, JOptionPane.DEFAULT_OPTION, JOptionPane.OK_CANCEL_OPTION);
|
|
JDialog dialog = pane.createDialog(this, "doh.robot");
|
|
dialog.setLocationRelativeTo(this);
|
|
dialog.show();
|
|
return ((Integer)pane.getValue()).intValue()==JOptionPane.OK_OPTION;
|
|
}
|
|
|
|
// mouse discovery code
|
|
public void setDocumentBounds(final double sec, final int x, final int y, final int w, final int h) throws Exception{
|
|
// call from JavaScript
|
|
// tells the Robot where the screen x,y of the upper left corner of the
|
|
// document are
|
|
// not screenX/Y of the window; really screenLeft/Top in IE, but not all
|
|
// browsers have this
|
|
log("> setDocumentBounds");
|
|
if(!isSecure(sec))
|
|
return;
|
|
if(!inited){
|
|
DOHRobot _this=applet();
|
|
log("initing doc bounds");
|
|
inited = true;
|
|
Point location=_this.getLocationOnScreen();
|
|
_this.lastMouseX = _this.docScreenX = location.x;
|
|
_this.lastMouseY = _this.docScreenY = location.y;
|
|
_this.docScreenXMax = _this.docScreenX + w;
|
|
_this.docScreenYMax = _this.docScreenY + h;
|
|
log("Doc bounds: "+docScreenX+","+docScreenY+" => "+docScreenXMax+","+docScreenYMax);
|
|
// compute difference between position and browser edge for future reference
|
|
_this.margin = getLocationOnScreen();
|
|
_this.margin.x -= x;
|
|
_this.margin.y -= y;
|
|
mouseSecurity=true;
|
|
}else{
|
|
log("ERROR: tried to reinit bounds?");
|
|
}
|
|
log("< setDocumentBounds");
|
|
}
|
|
|
|
// keyboard discovery code
|
|
private void _mapKey(char charCode, int keyindex, boolean shift,
|
|
boolean altgraph){
|
|
log("_mapKey: " + charCode);
|
|
// if character is not in map, add it
|
|
if(!charMap.containsKey(new Integer(charCode))){
|
|
log("Notified: " + (char) charCode);
|
|
KeyEvent event = new KeyEvent(applet(), 0, 0,
|
|
(shift ? KeyEvent.SHIFT_MASK : 0)
|
|
+ (altgraph ? KeyEvent.ALT_GRAPH_MASK : 0),
|
|
((Integer) vkKeys.get(keyindex)).intValue(),
|
|
(char) charCode);
|
|
charMap.put(new Integer(charCode), event);
|
|
log("Mapped char " + (char) charCode + " to KeyEvent " + event);
|
|
if(((char) charCode) >= 'a' && ((char) charCode) <= 'z'){
|
|
// put shifted version of a-z in automatically
|
|
int uppercharCode = (int) Character
|
|
.toUpperCase((char) charCode);
|
|
event = new KeyEvent(applet(), 0, 0, KeyEvent.SHIFT_MASK
|
|
+ (altgraph ? KeyEvent.ALT_GRAPH_MASK : 0),
|
|
((Integer) vkKeys.get(keyindex)).intValue(),
|
|
(char) uppercharCode);
|
|
charMap.put(new Integer(uppercharCode), event);
|
|
log("Mapped char " + (char) uppercharCode + " to KeyEvent "
|
|
+ event);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void _notified(final double sec, final String chars){
|
|
// decouple from JavaScript; thread join could hang it
|
|
Runnable thread = new Runnable(){
|
|
public void run(){
|
|
if(!isSecure(sec))
|
|
return;
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
keystring += chars;
|
|
if(altgraph && !shift){
|
|
shift = false;
|
|
// Set robot auto delay now that FF/Mac inited all of the keys.
|
|
// Good for DND.
|
|
robot.setAutoDelay(1);
|
|
try{
|
|
log(keystring);
|
|
int index = 0;
|
|
for (int i = 0; (i < vkKeys.size())
|
|
&& (index < keystring.length()); i++){
|
|
char c = keystring.charAt(index++);
|
|
_mapKey(c, i, false, false);
|
|
}
|
|
for (int i = 0; (i < vkKeys.size())
|
|
&& (index < keystring.length()); i++){
|
|
char c = keystring.charAt(index++);
|
|
_mapKey(c, i, true, false);
|
|
}
|
|
for (int i = 0; (i < vkKeys.size())
|
|
&& (index < keystring.length()); i++){
|
|
char c = keystring.charAt(index++);
|
|
_mapKey(c, i, false, true);
|
|
}
|
|
// notify DOH that the applet finished init
|
|
window.call("_onKeyboard", new Object[]{});
|
|
}catch(Exception e){
|
|
e.printStackTrace();
|
|
}
|
|
return null;
|
|
}else if(!shift){
|
|
shift = true;
|
|
}else{
|
|
shift = false;
|
|
altgraph = true;
|
|
}
|
|
pressNext();
|
|
// }
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
threadPool.execute(thread);
|
|
}
|
|
|
|
private void pressNext(){
|
|
Runnable thread = new Runnable(){
|
|
public void run(){
|
|
// first time, press shift (have to do it here instead of
|
|
// _notified to avoid IllegalThreadStateException on Mac)
|
|
log("starting up, " + shift + " " + altgraph);
|
|
if(shift){
|
|
robot.keyPress(KeyEvent.VK_SHIFT);
|
|
log("Pressing shift");
|
|
}
|
|
try{
|
|
if(altgraph){
|
|
robot.keyPress(KeyEvent.VK_ALT_GRAPH);
|
|
log("Pressing alt graph");
|
|
}
|
|
}catch(Exception e){
|
|
log("Error pressing alt graph");
|
|
e.printStackTrace();
|
|
_notified(key, "");
|
|
return;
|
|
}
|
|
window.call("_nextKeyGroup", new Object[]{ new Integer(vkKeys.size()) });
|
|
for (int keyindex = 0; keyindex < vkKeys.size(); keyindex++){
|
|
try{
|
|
log("Press "
|
|
+ ((Integer) vkKeys.get(keyindex)).intValue());
|
|
robot.keyPress(((Integer) vkKeys.get(keyindex))
|
|
.intValue());
|
|
log("Release "
|
|
+ ((Integer) vkKeys.get(keyindex)).intValue());
|
|
robot.keyRelease(((Integer) vkKeys.get(keyindex))
|
|
.intValue());
|
|
if(altgraph && (keyindex == (vkKeys.size() - 1))){
|
|
robot.keyRelease(KeyEvent.VK_ALT_GRAPH);
|
|
log("Releasing alt graph");
|
|
}
|
|
if(shift && (keyindex == (vkKeys.size() - 1))){
|
|
robot.keyRelease(KeyEvent.VK_SHIFT);
|
|
log("Releasing shift");
|
|
}
|
|
}catch(Exception e){
|
|
}
|
|
try{
|
|
log("Press space");
|
|
robot.keyPress(KeyEvent.VK_SPACE);
|
|
log("Release space");
|
|
robot.keyRelease(KeyEvent.VK_SPACE);
|
|
}catch(Exception e){
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
threadPool.execute(thread);
|
|
}
|
|
|
|
public void _initWheel(final double sec){
|
|
log("> initWheel");
|
|
Runnable thread=new Runnable(){
|
|
public void run(){
|
|
if(!isSecure(sec))
|
|
return;
|
|
Thread.yield();
|
|
// calibrate the mouse wheel now that textbox is focused
|
|
dir=1;
|
|
// fixed in 10.6.2 update 1 and 10.5.8 update 6:
|
|
// http://developer.apple.com/mac/library/releasenotes/CrossPlatform/JavaSnowLeopardUpdate1LeopardUpdate6RN/ResolvedIssues/ResolvedIssues.html
|
|
// Radar #6193836
|
|
if(os.indexOf("MAC") != -1){
|
|
// see if the version is greater than 10.5.8
|
|
String[] sfixedVersion = "10.5.8".split("\\.");
|
|
int[] fixedVersion = new int[3];
|
|
String[] sthisVersion = System.getProperty("os.version").split("\\.");
|
|
int[] thisVersion = new int[3];
|
|
for(int i=0; i<3; i++){
|
|
fixedVersion[i]=Integer.valueOf(sfixedVersion[i]).intValue();
|
|
thisVersion[i]=Integer.valueOf(sthisVersion[i]).intValue();
|
|
};
|
|
// 10.5.8, the fix level, should count as fixed
|
|
// on the other hand, 10.6.0 and 10.6.1 should not
|
|
boolean isFixed = !System.getProperty("os.version").equals("10.6.0")&&!System.getProperty("os.version").equals("10.6.1");
|
|
for(int i=0; i<fixedVersion.length&&isFixed; i++){
|
|
if(thisVersion[i]>fixedVersion[i]){
|
|
// definitely newer at this point
|
|
isFixed = true;
|
|
break;
|
|
}else if(thisVersion[i]<fixedVersion[i]){
|
|
// definitely older
|
|
isFixed = false;
|
|
break;
|
|
}
|
|
// equal; continue to next dot
|
|
|
|
}
|
|
// flip dir if not fixed
|
|
dir=isFixed?dir:-dir;
|
|
}
|
|
robot.mouseWheel(dir);
|
|
try{
|
|
Thread.sleep(100);
|
|
}catch(Exception e){}
|
|
log("< initWheel");
|
|
}
|
|
};
|
|
threadPool.execute(thread);
|
|
}
|
|
|
|
public void _initKeyboard(final double sec){
|
|
log("> initKeyboard");
|
|
// javascript entry point to discover the keyboard
|
|
if(charMap != null){
|
|
window.call("_onKeyboard", new Object[]{});
|
|
return;
|
|
}
|
|
Runnable thread = new Runnable(){
|
|
public void run(){
|
|
if(!isSecure(sec))
|
|
return;
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
charMap = new HashMap();
|
|
KeyEvent event = new KeyEvent(applet(), 0, 0, 0,
|
|
KeyEvent.VK_SPACE, ' ');
|
|
charMap.put(new Integer(32), event);
|
|
try{
|
|
// a-zA-Z0-9 + 29 others
|
|
vkKeys = new Vector();
|
|
for (char i = 'a'; i <= 'z'; i++){
|
|
vkKeys.add(new Integer(KeyEvent.class.getField(
|
|
"VK_" + Character.toUpperCase((char) i))
|
|
.getInt(null)));
|
|
}
|
|
for (char i = '0'; i <= '9'; i++){
|
|
vkKeys.add(new Integer(KeyEvent.class.getField(
|
|
"VK_" + Character.toUpperCase((char) i))
|
|
.getInt(null)));
|
|
}
|
|
int[] mykeys = new int[]{ KeyEvent.VK_COMMA,
|
|
KeyEvent.VK_MINUS, KeyEvent.VK_PERIOD,
|
|
KeyEvent.VK_SLASH, KeyEvent.VK_SEMICOLON,
|
|
KeyEvent.VK_LEFT_PARENTHESIS,
|
|
KeyEvent.VK_NUMBER_SIGN, KeyEvent.VK_PLUS,
|
|
KeyEvent.VK_RIGHT_PARENTHESIS,
|
|
KeyEvent.VK_UNDERSCORE,
|
|
KeyEvent.VK_EXCLAMATION_MARK, KeyEvent.VK_DOLLAR,
|
|
KeyEvent.VK_CIRCUMFLEX, KeyEvent.VK_AMPERSAND,
|
|
KeyEvent.VK_ASTERISK, KeyEvent.VK_QUOTEDBL,
|
|
KeyEvent.VK_LESS, KeyEvent.VK_GREATER,
|
|
KeyEvent.VK_BRACELEFT, KeyEvent.VK_BRACERIGHT,
|
|
KeyEvent.VK_COLON, KeyEvent.VK_BACK_QUOTE,
|
|
KeyEvent.VK_QUOTE, KeyEvent.VK_OPEN_BRACKET,
|
|
KeyEvent.VK_BACK_SLASH, KeyEvent.VK_CLOSE_BRACKET,
|
|
KeyEvent.VK_EQUALS };
|
|
for (int i = 0; i < mykeys.length; i++){
|
|
vkKeys.add(new Integer(mykeys[i]));
|
|
}
|
|
}catch(Exception e){
|
|
e.printStackTrace();
|
|
}
|
|
robot.setAutoDelay(1);
|
|
// prime the event pump for Google Chome - so fast it doesn't even stop to listen for key events!
|
|
// send spaces until JS says to stop
|
|
int count=0;
|
|
boolean waitingOnSpace = true;
|
|
do{
|
|
log("Pressed space");
|
|
robot.keyPress(KeyEvent.VK_SPACE);
|
|
robot.keyRelease(KeyEvent.VK_SPACE);
|
|
count++;
|
|
waitingOnSpace = ((Boolean)window.eval("doh.robot._spaceReceived")).equals(Boolean.FALSE);
|
|
log("JS still waiting on a space? "+waitingOnSpace);
|
|
}while(count<500&&waitingOnSpace);
|
|
robot.keyPress(KeyEvent.VK_ENTER);
|
|
robot.keyRelease(KeyEvent.VK_ENTER);
|
|
robot.setAutoDelay(0);
|
|
log("< initKeyboard");
|
|
pressNext();
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
threadPool.execute(thread);
|
|
}
|
|
|
|
public void typeKey(double sec, final int charCode, final int keyCode,
|
|
final boolean alt, final boolean ctrl, final boolean shift, final boolean meta,
|
|
final int delay, final boolean async){
|
|
if(!isSecure(sec))
|
|
return;
|
|
// called by doh.robot._keyPress
|
|
// see it for details
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
try{
|
|
log("> typeKey Robot " + charCode + ", " + keyCode + ", " + async);
|
|
KeyPressThread thread = new KeyPressThread(charCode,
|
|
keyCode, alt, ctrl, shift, meta, delay);
|
|
if(async){
|
|
Thread asyncthread=new Thread(thread);
|
|
asyncthread.start();
|
|
}else{
|
|
threadPool.execute(thread);
|
|
}
|
|
log("< typeKey Robot");
|
|
}catch(Exception e){
|
|
log("Error calling typeKey");
|
|
e.printStackTrace();
|
|
}
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
public void upKey(double sec, final int charCode, final int keyCode, final int delay){
|
|
// called by doh.robot.keyDown
|
|
// see it for details
|
|
// a nice name like "keyUp" is reserved in Java
|
|
if(!isSecure(sec))
|
|
return;
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
log("> upKey Robot " + charCode + ", " + keyCode);
|
|
KeyUpThread thread = new KeyUpThread(charCode, keyCode, delay);
|
|
threadPool.execute(thread);
|
|
log("< upKey Robot");
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
public void downKey(double sec, final int charCode, final int keyCode, final int delay){
|
|
// called by doh.robot.keyUp
|
|
// see it for details
|
|
// a nice name like "keyDown" is reserved in Java
|
|
if(!isSecure(sec))
|
|
return;
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
log("> downKey Robot " + charCode + ", " + keyCode);
|
|
KeyDownThread thread = new KeyDownThread(charCode, keyCode, delay);
|
|
threadPool.execute(thread);
|
|
log("< downKey Robot");
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
public void pressMouse(double sec, final boolean left,
|
|
final boolean middle, final boolean right, final int delay){
|
|
if(!isSecure(sec))
|
|
return;
|
|
// called by doh.robot.mousePress
|
|
// see it for details
|
|
// a nice name like "mousePress" is reserved in Java
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
log("> mousePress Robot " + left + ", " + middle + ", " + right);
|
|
MousePressThread thread = new MousePressThread(
|
|
(left ? InputEvent.BUTTON1_MASK : 0)
|
|
+ (middle ? InputEvent.BUTTON2_MASK : 0)
|
|
+ (right ? InputEvent.BUTTON3_MASK : 0), delay);
|
|
threadPool.execute(thread);
|
|
log("< mousePress Robot");
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
public void releaseMouse(double sec, final boolean left,
|
|
final boolean middle, final boolean right, final int delay){
|
|
if(!isSecure(sec))
|
|
return;
|
|
// called by doh.robot.mouseRelease
|
|
// see it for details
|
|
// a nice name like "mouseRelease" is reserved in Java
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
log("> mouseRelease Robot " + left + ", " + middle + ", "
|
|
+ right);
|
|
MouseReleaseThread thread = new MouseReleaseThread(
|
|
(left ? InputEvent.BUTTON1_MASK : 0)
|
|
+ (middle ? InputEvent.BUTTON2_MASK : 0)
|
|
+ (right ? InputEvent.BUTTON3_MASK : 0), delay
|
|
);
|
|
threadPool.execute(thread);
|
|
log("< mouseRelease Robot");
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
protected boolean destinationInView(int x, int y){
|
|
return !(x > docScreenXMax || y > docScreenYMax || x < docScreenX || y < docScreenY);
|
|
}
|
|
|
|
public void moveMouse(double sec, final int x1, final int y1, final int d, final int duration){
|
|
// called by doh.robot.mouseMove
|
|
// see it for details
|
|
// a nice name like "mouseMove" is reserved in Java
|
|
if(!isSecure(sec))
|
|
return;
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
int x = x1 + docScreenX;
|
|
int y = y1 + docScreenY;
|
|
if(!destinationInView(x,y)){
|
|
// TODO: try to scroll view
|
|
log("Request to mouseMove denied");
|
|
return null;
|
|
}
|
|
int delay = d;
|
|
log("> mouseMove Robot " + x + ", " + y);
|
|
MouseMoveThread thread = new MouseMoveThread(x, y, delay,
|
|
duration);
|
|
threadPool.execute(thread);
|
|
log("< mouseMove Robot");
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
public void wheelMouse(double sec, final int amount, final int delay, final int duration){
|
|
// called by doh.robot.mouseWheel
|
|
// see it for details
|
|
if(!isSecure(sec))
|
|
return;
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
MouseWheelThread thread = new MouseWheelThread(amount, delay, duration);
|
|
threadPool.execute(thread);
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
private int getVKCode(int charCode, int keyCode){
|
|
int keyboardCode = 0;
|
|
if(charCode >= 32){
|
|
// if it is printable, then it lives in our hashmap
|
|
KeyEvent event = (KeyEvent) charMap.get(new Integer(charCode));
|
|
keyboardCode = event.getKeyCode();
|
|
}
|
|
else{
|
|
switch (keyCode){
|
|
case 13:
|
|
keyboardCode = KeyEvent.VK_ENTER;
|
|
break;
|
|
case 8:
|
|
keyboardCode = KeyEvent.VK_BACK_SPACE;
|
|
break;
|
|
case 25:// shift tab for Safari
|
|
case 9:
|
|
keyboardCode = KeyEvent.VK_TAB;
|
|
break;
|
|
case 12:
|
|
keyboardCode = KeyEvent.VK_CLEAR;
|
|
break;
|
|
case 16:
|
|
keyboardCode = KeyEvent.VK_SHIFT;
|
|
break;
|
|
case 17:
|
|
keyboardCode = KeyEvent.VK_CONTROL;
|
|
break;
|
|
case 18:
|
|
keyboardCode = KeyEvent.VK_ALT;
|
|
break;
|
|
case 63250:
|
|
case 19:
|
|
keyboardCode = KeyEvent.VK_PAUSE;
|
|
break;
|
|
case 20:
|
|
keyboardCode = KeyEvent.VK_CAPS_LOCK;
|
|
break;
|
|
case 27:
|
|
keyboardCode = KeyEvent.VK_ESCAPE;
|
|
break;
|
|
case 32:
|
|
log("it's a space");
|
|
keyboardCode = KeyEvent.VK_SPACE;
|
|
break;
|
|
case 63276:
|
|
case 33:
|
|
keyboardCode = KeyEvent.VK_PAGE_UP;
|
|
break;
|
|
case 63277:
|
|
case 34:
|
|
keyboardCode = KeyEvent.VK_PAGE_DOWN;
|
|
break;
|
|
case 63275:
|
|
case 35:
|
|
keyboardCode = KeyEvent.VK_END;
|
|
break;
|
|
case 63273:
|
|
case 36:
|
|
keyboardCode = KeyEvent.VK_HOME;
|
|
break;
|
|
|
|
/**
|
|
* Constant for the <b>left</b> arrow key.
|
|
*/
|
|
case 63234:
|
|
case 37:
|
|
keyboardCode = KeyEvent.VK_LEFT;
|
|
break;
|
|
|
|
/**
|
|
* Constant for the <b>up</b> arrow key.
|
|
*/
|
|
case 63232:
|
|
case 38:
|
|
keyboardCode = KeyEvent.VK_UP;
|
|
break;
|
|
|
|
/**
|
|
* Constant for the <b>right</b> arrow key.
|
|
*/
|
|
case 63235:
|
|
case 39:
|
|
keyboardCode = KeyEvent.VK_RIGHT;
|
|
break;
|
|
|
|
/**
|
|
* Constant for the <b>down</b> arrow key.
|
|
*/
|
|
case 63233:
|
|
case 40:
|
|
keyboardCode = KeyEvent.VK_DOWN;
|
|
break;
|
|
case 63272:
|
|
case 46:
|
|
keyboardCode = KeyEvent.VK_DELETE;
|
|
break;
|
|
case 224:
|
|
case 91:
|
|
keyboardCode = KeyEvent.VK_META;
|
|
break;
|
|
case 63289:
|
|
case 144:
|
|
keyboardCode = KeyEvent.VK_NUM_LOCK;
|
|
break;
|
|
case 63249:
|
|
case 145:
|
|
keyboardCode = KeyEvent.VK_SCROLL_LOCK;
|
|
break;
|
|
|
|
/** Constant for the F1 function key. */
|
|
case 63236:
|
|
case 112:
|
|
keyboardCode = KeyEvent.VK_F1;
|
|
break;
|
|
|
|
/** Constant for the F2 function key. */
|
|
case 63237:
|
|
case 113:
|
|
keyboardCode = KeyEvent.VK_F2;
|
|
break;
|
|
|
|
/** Constant for the F3 function key. */
|
|
case 63238:
|
|
case 114:
|
|
keyboardCode = KeyEvent.VK_F3;
|
|
break;
|
|
|
|
/** Constant for the F4 function key. */
|
|
case 63239:
|
|
case 115:
|
|
keyboardCode = KeyEvent.VK_F4;
|
|
break;
|
|
|
|
/** Constant for the F5 function key. */
|
|
case 63240:
|
|
case 116:
|
|
keyboardCode = KeyEvent.VK_F5;
|
|
break;
|
|
|
|
/** Constant for the F6 function key. */
|
|
case 63241:
|
|
case 117:
|
|
keyboardCode = KeyEvent.VK_F6;
|
|
break;
|
|
|
|
/** Constant for the F7 function key. */
|
|
case 63242:
|
|
case 118:
|
|
keyboardCode = KeyEvent.VK_F7;
|
|
break;
|
|
|
|
/** Constant for the F8 function key. */
|
|
case 63243:
|
|
case 119:
|
|
keyboardCode = KeyEvent.VK_F8;
|
|
break;
|
|
|
|
/** Constant for the F9 function key. */
|
|
case 63244:
|
|
case 120:
|
|
keyboardCode = KeyEvent.VK_F9;
|
|
break;
|
|
|
|
/** Constant for the F10 function key. */
|
|
case 63245:
|
|
case 121:
|
|
keyboardCode = KeyEvent.VK_F10;
|
|
break;
|
|
|
|
/** Constant for the F11 function key. */
|
|
case 63246:
|
|
case 122:
|
|
keyboardCode = KeyEvent.VK_F11;
|
|
break;
|
|
|
|
/** Constant for the F12 function key. */
|
|
case 63247:
|
|
case 123:
|
|
keyboardCode = KeyEvent.VK_F12;
|
|
break;
|
|
|
|
/**
|
|
* Constant for the F13 function key.
|
|
*
|
|
* @since 1.2
|
|
*/
|
|
/*
|
|
* F13 - F24 are used on IBM 3270 keyboard; break; use
|
|
* random range for constants.
|
|
*/
|
|
case 124:
|
|
keyboardCode = KeyEvent.VK_F13;
|
|
break;
|
|
|
|
/**
|
|
* Constant for the F14 function key.
|
|
*
|
|
* @since 1.2
|
|
*/
|
|
case 125:
|
|
keyboardCode = KeyEvent.VK_F14;
|
|
break;
|
|
|
|
/**
|
|
* Constant for the F15 function key.
|
|
*
|
|
* @since 1.2
|
|
*/
|
|
case 126:
|
|
keyboardCode = KeyEvent.VK_F15;
|
|
break;
|
|
|
|
case 63302:
|
|
case 45:
|
|
keyboardCode = KeyEvent.VK_INSERT;
|
|
break;
|
|
case 47:
|
|
keyboardCode = KeyEvent.VK_HELP;
|
|
break;
|
|
default:
|
|
keyboardCode = keyCode;
|
|
|
|
}
|
|
}
|
|
log("Attempting to type " + (char) charCode + ":"
|
|
+ charCode + " " + keyCode);
|
|
log("Converted to " + keyboardCode);
|
|
return keyboardCode;
|
|
}
|
|
|
|
private boolean isUnsafe(int keyboardCode){
|
|
// run through exemption list
|
|
log("ctrl: "+ctrl+", alt: "+alt+", shift: "+shift);
|
|
if(((ctrl || alt) && keyboardCode == KeyEvent.VK_ESCAPE)
|
|
|| (alt && keyboardCode == KeyEvent.VK_TAB)
|
|
|| (ctrl && alt && keyboardCode == KeyEvent.VK_DELETE)){
|
|
log("You are not allowed to press this key combination!");
|
|
return true;
|
|
// bugged keys cases go next
|
|
}else{
|
|
log("Safe to press.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private boolean disableNumlock(int vk, boolean shift){
|
|
boolean result = !numlockDisabled&&shift
|
|
&&os.indexOf("WINDOWS")!=-1
|
|
&&toolkit.getLockingKeyState(KeyEvent.VK_NUM_LOCK) // only works on Windows
|
|
&&(
|
|
// any numpad buttons are suspect
|
|
vk==KeyEvent.VK_LEFT
|
|
||vk==KeyEvent.VK_UP
|
|
||vk==KeyEvent.VK_RIGHT
|
|
||vk==KeyEvent.VK_DOWN
|
|
||vk==KeyEvent.VK_HOME
|
|
||vk==KeyEvent.VK_END
|
|
||vk==KeyEvent.VK_PAGE_UP
|
|
||vk==KeyEvent.VK_PAGE_DOWN
|
|
);
|
|
log("disable numlock: "+result);
|
|
return result;
|
|
}
|
|
|
|
private void _typeKey(final int cCode, final int kCode, final boolean a,
|
|
final boolean c, final boolean s, final boolean m){
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
int charCode = cCode;
|
|
int keyCode = kCode;
|
|
boolean alt = a;
|
|
boolean ctrl = c;
|
|
boolean shift = s;
|
|
boolean meta = m;
|
|
boolean altgraph = false;
|
|
log("> _typeKey Robot " + charCode + ", " + keyCode);
|
|
try{
|
|
int keyboardCode=getVKCode(charCode, keyCode);
|
|
if(charCode >= 32){
|
|
// if it is printable, then it lives in our hashmap
|
|
KeyEvent event = (KeyEvent) charMap.get(new Integer(charCode));
|
|
// see if we need to press shift to generate this
|
|
// character
|
|
if(!shift){
|
|
shift = event.isShiftDown();
|
|
}
|
|
altgraph = event.isAltGraphDown();
|
|
keyboardCode = event.getKeyCode();
|
|
}
|
|
|
|
// Java bug: on Windows, shift+arrow key unpresses shift when numlock is on.
|
|
// See: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4838497
|
|
boolean disableNumlock=disableNumlock(keyboardCode,shift||applet().shift);
|
|
// run through exemption list
|
|
if(!isUnsafe(keyboardCode)){
|
|
if(shift){
|
|
log("Pressing shift");
|
|
robot.keyPress(KeyEvent.VK_SHIFT);
|
|
}
|
|
if(alt){
|
|
log("Pressing alt");
|
|
robot.keyPress(KeyEvent.VK_ALT);
|
|
}
|
|
if(altgraph){
|
|
log("Pressing altgraph");
|
|
robot.keyPress(KeyEvent.VK_ALT_GRAPH);
|
|
}
|
|
if(ctrl){
|
|
log("Pressing ctrl");
|
|
robot.keyPress(KeyEvent.VK_CONTROL);
|
|
}
|
|
if(meta){
|
|
log("Pressing meta");
|
|
robot.keyPress(KeyEvent.VK_META);
|
|
}
|
|
if(disableNumlock){
|
|
robot.keyPress(KeyEvent.VK_NUM_LOCK);
|
|
robot.keyRelease(KeyEvent.VK_NUM_LOCK);
|
|
numlockDisabled=true;
|
|
}else if(numlockDisabled&&!(applet().shift||shift)){
|
|
// only turn it back on when the user is finished pressing shifted arrow keys
|
|
robot.keyPress(KeyEvent.VK_NUM_LOCK);
|
|
robot.keyRelease(KeyEvent.VK_NUM_LOCK);
|
|
numlockDisabled=false;
|
|
}
|
|
if(keyboardCode != KeyEvent.VK_SHIFT
|
|
&& keyboardCode != KeyEvent.VK_ALT
|
|
&& keyboardCode != KeyEvent.VK_ALT_GRAPH
|
|
&& keyboardCode != KeyEvent.VK_CONTROL
|
|
&& keyboardCode != KeyEvent.VK_META){
|
|
try{
|
|
robot.keyPress(keyboardCode);
|
|
robot.keyRelease(keyboardCode);
|
|
}catch(Exception e){
|
|
log("Error while actually typing a key");
|
|
e.printStackTrace();
|
|
}
|
|
|
|
}
|
|
if(ctrl){
|
|
robot.keyRelease(KeyEvent.VK_CONTROL);
|
|
ctrl = false;
|
|
}
|
|
if(alt){
|
|
robot.keyRelease(KeyEvent.VK_ALT);
|
|
alt = false;
|
|
}
|
|
if(altgraph){
|
|
robot.keyRelease(KeyEvent.VK_ALT_GRAPH);
|
|
altgraph = false;
|
|
}
|
|
if(shift){
|
|
log("Releasing shift");
|
|
robot.keyRelease(KeyEvent.VK_SHIFT);
|
|
shift = false;
|
|
}
|
|
if(meta){
|
|
log("Releasing meta");
|
|
robot.keyRelease(KeyEvent.VK_META);
|
|
meta = false;
|
|
}
|
|
}
|
|
}catch(Exception e){
|
|
log("Error in _typeKey");
|
|
e.printStackTrace();
|
|
}
|
|
log("< _typeKey Robot");
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
public boolean hasFocus(){
|
|
// sanity check to make sure the robot isn't clicking outside the window when the browser is minimized for instance
|
|
try{
|
|
boolean result= ((Boolean) window
|
|
.eval("var result=false;if(window.parent.document.hasFocus){result=window.parent.document.hasFocus();}else{result=true;}result;"))
|
|
.booleanValue();
|
|
if(!result){
|
|
// can happen for instance if the browser minimized itself, or if there is another applet on the page.
|
|
// recompute window,mouse positions to see if it is still safe to continue.
|
|
log("Document focus lost. Recomputing window position");
|
|
Point p = getLocationOnScreen();
|
|
log("Old root: "+docScreenX+" "+docScreenY);
|
|
docScreenX=p.x-margin.x;
|
|
docScreenY=p.y-margin.y;
|
|
log("New root: "+docScreenX+" "+docScreenY);
|
|
docScreenXMax=docScreenX+((Integer)window.eval("window.parent.document.getElementById('dohrobotview').offsetLeft")).intValue();
|
|
docScreenYMax=docScreenY+((Integer)window.eval("window.parent.document.getElementById('dohrobotview').offsetTop")).intValue();
|
|
// bring browser to the front again.
|
|
// if the window just blurred and moved, key events will again be directed to the window.
|
|
// if an applet stole focus, focus will still be directed to the applet; the test script will ultimately have to click something to get back to a normal state.
|
|
window.eval("window.parent.focus();");
|
|
// recompute mouse position
|
|
return isSecure(this.key);
|
|
}else{
|
|
return result;
|
|
}
|
|
}catch(Exception e){
|
|
// runs even after you close the window!
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Threads for common Robot tasks
|
|
// (so as not to tie up the browser rendering thread!)
|
|
// declared inside so they have private access to the robot
|
|
// we do *not* want to expose that guy!
|
|
private class ProfilingThread implements Runnable{
|
|
protected long delay=0;
|
|
protected long duration=0;
|
|
private long start;
|
|
private long oldDelay;
|
|
protected void startProfiling(){
|
|
// error correct
|
|
if(delay>0){
|
|
oldDelay=delay;
|
|
delay-=timingError+(duration>0?timingError:0);
|
|
log("Timing error: "+timingError);
|
|
if(delay<1){
|
|
if(duration>0){ duration=Math.max(duration+delay,1); }
|
|
delay=1;
|
|
}
|
|
start=System.currentTimeMillis();
|
|
}else{
|
|
// assumption is that only doh.robot.typeKeys actually uses delay/needs this level of error correcting
|
|
timingError=0;
|
|
}
|
|
}
|
|
protected void endProfiling(){
|
|
// adaptively correct timingError
|
|
if(delay>0){
|
|
long end=System.currentTimeMillis();
|
|
timingError+=(end-start)-oldDelay;
|
|
}
|
|
}
|
|
public void run(){}
|
|
}
|
|
|
|
// Unclear why we have to fire keypress in a separate thread.
|
|
// Since delay is no longer used, maybe this code can be simplified.
|
|
final private class KeyPressThread extends ProfilingThread{
|
|
private int charCode;
|
|
private int keyCode;
|
|
private boolean alt;
|
|
private boolean ctrl;
|
|
private boolean shift;
|
|
private boolean meta;
|
|
|
|
public KeyPressThread(int charCode, int keyCode, boolean alt,
|
|
boolean ctrl, boolean shift, boolean meta, int delay){
|
|
log("KeyPressThread constructor " + charCode + ", " + keyCode);
|
|
this.charCode = charCode;
|
|
this.keyCode = keyCode;
|
|
this.alt = alt;
|
|
this.ctrl = ctrl;
|
|
this.shift = shift;
|
|
this.meta = meta;
|
|
this.delay = delay;
|
|
}
|
|
|
|
public void run(){
|
|
try{
|
|
startProfiling();
|
|
// in different order so async works
|
|
while(!hasFocus()){
|
|
Thread.sleep(1000);
|
|
}
|
|
Thread.sleep(delay);
|
|
log("> run KeyPressThread");
|
|
|
|
_typeKey(charCode, keyCode, alt, ctrl, shift, meta);
|
|
|
|
endProfiling();
|
|
}catch(Exception e){
|
|
log("Bad parameters passed to _typeKey");
|
|
e.printStackTrace();
|
|
}
|
|
log("< run KeyPressThread");
|
|
|
|
}
|
|
}
|
|
|
|
final private class KeyDownThread extends ProfilingThread{
|
|
private int charCode;
|
|
private int keyCode;
|
|
|
|
public KeyDownThread(int charCode, int keyCode, int delay){
|
|
log("KeyDownThread constructor " + charCode + ", " + keyCode);
|
|
this.charCode = charCode;
|
|
this.keyCode = keyCode;
|
|
this.delay = delay;
|
|
}
|
|
|
|
public void run(){
|
|
try{
|
|
Thread.sleep(delay);
|
|
log("> run KeyDownThread");
|
|
while(!hasFocus()){
|
|
Thread.sleep(1000);
|
|
}
|
|
int vkCode=getVKCode(charCode, keyCode);
|
|
if(charCode >= 32){
|
|
// if it is printable, then it lives in our hashmap
|
|
KeyEvent event = (KeyEvent) charMap.get(new Integer(charCode));
|
|
// see if we need to press shift to generate this
|
|
// character
|
|
if(event.isShiftDown()){
|
|
robot.keyPress(KeyEvent.VK_SHIFT);
|
|
shift=true;
|
|
}
|
|
if(event.isAltGraphDown()){
|
|
robot.keyPress(KeyEvent.VK_ALT_GRAPH);
|
|
altgraph=true;
|
|
}
|
|
}else{
|
|
if(vkCode==KeyEvent.VK_ALT){
|
|
alt=true;
|
|
}else if(vkCode==KeyEvent.VK_CONTROL){
|
|
ctrl=true;
|
|
}else if(vkCode==KeyEvent.VK_SHIFT){
|
|
shift=true;
|
|
}else if(vkCode==KeyEvent.VK_ALT_GRAPH){
|
|
altgraph=true;
|
|
}else if(vkCode==KeyEvent.VK_META){
|
|
meta=true;
|
|
}else if(disableNumlock(vkCode,shift)){
|
|
robot.keyPress(KeyEvent.VK_NUM_LOCK);
|
|
robot.keyRelease(KeyEvent.VK_NUM_LOCK);
|
|
numlockDisabled=true;
|
|
}
|
|
}
|
|
if(!isUnsafe(vkCode)){
|
|
robot.keyPress(vkCode);
|
|
}
|
|
}catch(Exception e){
|
|
log("Bad parameters passed to downKey");
|
|
e.printStackTrace();
|
|
}
|
|
log("< run KeyDownThread");
|
|
|
|
}
|
|
}
|
|
|
|
final private class KeyUpThread extends ProfilingThread{
|
|
private int charCode;
|
|
private int keyCode;
|
|
|
|
public KeyUpThread(int charCode, int keyCode, int delay){
|
|
log("KeyUpThread constructor " + charCode + ", " + keyCode);
|
|
this.charCode = charCode;
|
|
this.keyCode = keyCode;
|
|
this.delay = delay;
|
|
}
|
|
|
|
public void run(){
|
|
try{
|
|
Thread.sleep(delay);
|
|
log("> run KeyUpThread");
|
|
while(!hasFocus()){
|
|
Thread.sleep(1000);
|
|
}
|
|
int vkCode=getVKCode(charCode, keyCode);
|
|
if(charCode >= 32){
|
|
// if it is printable, then it lives in our hashmap
|
|
KeyEvent event = (KeyEvent) charMap.get(new Integer(charCode));
|
|
// see if we need to press shift to generate this
|
|
// character
|
|
if(event.isShiftDown()){
|
|
robot.keyRelease(KeyEvent.VK_SHIFT);
|
|
shift=false;
|
|
}
|
|
if(event.isAltGraphDown()){
|
|
robot.keyRelease(KeyEvent.VK_ALT_GRAPH);
|
|
altgraph=false;
|
|
}
|
|
}else{
|
|
if(vkCode==KeyEvent.VK_ALT){
|
|
alt=false;
|
|
}else if(vkCode==KeyEvent.VK_CONTROL){
|
|
ctrl=false;
|
|
}else if(vkCode==KeyEvent.VK_SHIFT){
|
|
shift=false;
|
|
if(numlockDisabled){
|
|
robot.keyPress(KeyEvent.VK_NUM_LOCK);
|
|
robot.keyRelease(KeyEvent.VK_NUM_LOCK);
|
|
numlockDisabled=false;
|
|
}
|
|
}else if(vkCode==KeyEvent.VK_ALT_GRAPH){
|
|
altgraph=false;
|
|
}else if(vkCode==KeyEvent.VK_META){
|
|
meta=false;
|
|
}
|
|
}
|
|
robot.keyRelease(vkCode);
|
|
}catch(Exception e){
|
|
log("Bad parameters passed to upKey");
|
|
e.printStackTrace();
|
|
}
|
|
log("< run KeyUpThread");
|
|
|
|
}
|
|
}
|
|
|
|
final private class MousePressThread extends ProfilingThread{
|
|
private int mask;
|
|
|
|
public MousePressThread(int mask, int delay){
|
|
this.mask = mask;
|
|
this.delay = delay;
|
|
}
|
|
|
|
public void run(){
|
|
try{
|
|
Thread.sleep(delay);
|
|
log("> run MousePressThread");
|
|
while(!hasFocus()){
|
|
Thread.sleep(1000);
|
|
}
|
|
robot.mousePress(mask);
|
|
robot.waitForIdle();
|
|
}catch(Exception e){
|
|
log("Bad parameters passed to mousePress");
|
|
e.printStackTrace();
|
|
}
|
|
log("< run MousePressThread");
|
|
|
|
}
|
|
}
|
|
|
|
final private class MouseReleaseThread extends ProfilingThread{
|
|
private int mask;
|
|
|
|
public MouseReleaseThread(int mask, int delay){
|
|
this.mask = mask;
|
|
this.delay = delay;
|
|
}
|
|
|
|
public void run(){
|
|
try{
|
|
Thread.sleep(delay);
|
|
log("> run MouseReleaseThread ");
|
|
while(!hasFocus()){
|
|
Thread.sleep(1000);
|
|
}
|
|
robot.mouseRelease(mask);
|
|
robot.waitForIdle();
|
|
}catch(Exception e){
|
|
log("Bad parameters passed to mouseRelease");
|
|
e.printStackTrace();
|
|
}
|
|
|
|
log("< run MouseReleaseThread ");
|
|
|
|
}
|
|
}
|
|
|
|
final private class MouseMoveThread extends ProfilingThread{
|
|
private int x;
|
|
private int y;
|
|
|
|
public MouseMoveThread(int x, int y, int delay, int duration){
|
|
this.x = x;
|
|
this.y = y;
|
|
this.delay = delay;
|
|
this.duration = duration;
|
|
}
|
|
|
|
public double easeInOutQuad(double t, double b, double c, double d){
|
|
t /= d / 2;
|
|
if(t < 1)
|
|
return c / 2 * t * t + b;
|
|
t--;
|
|
return -c / 2 * (t * (t - 2) - 1) + b;
|
|
};
|
|
|
|
public void run(){
|
|
try{
|
|
Thread.sleep(delay);
|
|
log("> run MouseMoveThread " + x + ", " + y);
|
|
while(!hasFocus()){
|
|
Thread.sleep(1000);
|
|
}
|
|
int x1 = lastMouseX;
|
|
int x2 = x;
|
|
int y1 = lastMouseY;
|
|
int y2 = y;
|
|
// shrink range by 1 px on both ends
|
|
// manually move this 1px to trip DND code
|
|
if(x1 != x2){
|
|
int dx = x - lastMouseX;
|
|
if(dx > 0){
|
|
x1 += 1;
|
|
x2 -= 1;
|
|
}else{
|
|
x1 -= 1;
|
|
x2 += 1;
|
|
}
|
|
}
|
|
if(y1 != y2){
|
|
int dy = y - lastMouseY;
|
|
if(dy > 0){
|
|
y1 += 1;
|
|
y2 -= 1;
|
|
}else{
|
|
y1 -= 1;
|
|
y2 += 1;
|
|
}
|
|
|
|
}
|
|
// manual precision
|
|
robot.setAutoWaitForIdle(false);
|
|
int intermediateSteps = duration==1?0: // duration==1 -> user wants to jump the mouse
|
|
((((int)Math.ceil(Math.log(duration+1)))|1)); // |1 to ensure an odd # of intermediate steps for sensible interpolation
|
|
// assumption: intermediateSteps will always be >=0
|
|
int delay = (int)duration/(intermediateSteps+1); // +1 to include last move
|
|
// First mouse movement fires at t=0 to official last know position of the mouse.
|
|
robot.mouseMove(lastMouseX, lastMouseY);
|
|
long start,end;
|
|
|
|
// Shift lastMouseX/Y in the direction of the movement for interpolating over the smaller interval.
|
|
lastMouseX=x1;
|
|
lastMouseY=y1;
|
|
// Now interpolate mouse movement from (lastMouseX=x1,lastMouseY=y1) to (x2,y2)
|
|
// precondition: the amount of time that has passed since the first mousemove is 0*delay.
|
|
// invariant: each time you end an iteration, after you increment t, the amount of time that has passed is t*delay
|
|
int timingError=0;
|
|
for (int t = 0; t < intermediateSteps; t++){
|
|
start=new Date().getTime();
|
|
Thread.sleep(delay);
|
|
x1 = (int) easeInOutQuad((double) t, (double) lastMouseX,
|
|
(double) x2 - lastMouseX, (double) intermediateSteps-1);
|
|
y1 = (int) easeInOutQuad((double) t, (double) lastMouseY,
|
|
(double) y2 - lastMouseY, (double) intermediateSteps-1);
|
|
//log("("+x1+","+y1+")");
|
|
robot.mouseMove(x1, y1);
|
|
end=new Date().getTime();
|
|
// distribute error among remaining steps
|
|
timingError=(((int)(end-start))-delay)/(intermediateSteps-t);
|
|
log("mouseMove timing error: "+timingError);
|
|
delay=Math.max(delay-(int)timingError,1);
|
|
}
|
|
// postconditions:
|
|
// t=intermediateSteps
|
|
// intermediateSteps*delay time has passed,
|
|
// time remaining = duration-intermediateSteps*delay = (steps+1)*delay-intermediateSteps*delay = delay
|
|
// You theoretically need 1 more delay for the whole duration to have passed.
|
|
// In practice, you want less than that due to roundoff errors in Java's clock granularity.
|
|
Thread.sleep(delay);
|
|
robot.mouseMove(x, y);
|
|
robot.setAutoWaitForIdle(true);
|
|
|
|
//log("mouseMove statistics: duration= "+duration+" steps="+intermediateSteps+" delay="+delay);
|
|
//log("mouseMove discrepency: "+(date2-date-duration)+"ms");
|
|
lastMouseX = x;
|
|
lastMouseY = y;
|
|
}catch(Exception e){
|
|
log("Bad parameters passed to mouseMove");
|
|
e.printStackTrace();
|
|
}
|
|
|
|
log("< run MouseMoveThread");
|
|
|
|
}
|
|
}
|
|
|
|
final private class MouseWheelThread extends ProfilingThread{
|
|
private int amount;
|
|
|
|
public MouseWheelThread(int amount, int delay, int duration){
|
|
this.amount = amount;
|
|
this.delay = delay;
|
|
this.duration = duration;
|
|
}
|
|
|
|
public void run(){
|
|
try{
|
|
Thread.sleep(delay);
|
|
log("> run MouseWheelThread " + amount);
|
|
while(!hasFocus()){
|
|
Thread.sleep(1000);
|
|
}
|
|
robot.setAutoDelay(Math.max((int)duration/Math.abs(amount),1));
|
|
for(int i=0; i<Math.abs(amount); i++){
|
|
robot.mouseWheel(amount>0?dir:-dir);
|
|
}
|
|
robot.setAutoDelay(1);
|
|
}catch(Exception e){
|
|
log("Bad parameters passed to mouseWheel");
|
|
e.printStackTrace();
|
|
}
|
|
log("< run MouseWheelThread ");
|
|
}
|
|
}
|
|
|
|
final private class RobotSecurityManager extends SecurityManager{
|
|
// The applet's original security manager.
|
|
// There is a bug in some people's Safaris that causes Safari to
|
|
// basically hang on liveconnect calls.
|
|
// Our security manager fixes it.
|
|
|
|
private boolean isActive = false;
|
|
private boolean needsSecurityManager = false;
|
|
private SecurityManager oldsecurity = null;
|
|
|
|
public RobotSecurityManager(boolean needsSecurityManager, SecurityManager oldsecurity){
|
|
this.needsSecurityManager = needsSecurityManager;
|
|
this.oldsecurity = oldsecurity;
|
|
}
|
|
|
|
public boolean checkTopLevelWindow(Object window){
|
|
// If our users temporarily accept our cert for a session,
|
|
// then use the same session to browse to a malicious website also using our applet,
|
|
// that website can automatically execute the applet.
|
|
// To resolve this issue, RobotSecurityManager overrides checkTopLevelWindow
|
|
// to check the JVM to see if there are other instances of the applet running on different domains.
|
|
// If there are, it prompts the user to confirm that they want to run the applet before continuing.
|
|
|
|
// null is not supposed to be allowed
|
|
// so we allow it to distinguish our security manager.
|
|
if(window == null){
|
|
isActive = !isActive;
|
|
log("Active is now " + isActive);
|
|
}
|
|
return window == null ? true : oldsecurity
|
|
.checkTopLevelWindow(window);
|
|
}
|
|
|
|
public void checkPermission(Permission p){
|
|
// liveconnect SocketPermission resolve takes
|
|
// FOREVER (like 6 seconds) in Safari 3
|
|
// Java does like 50 of these on the first JS call
|
|
// 6*50=300 seconds!
|
|
if(isActive && needsSecurityManager
|
|
&& java.net.SocketPermission.class.isInstance(p)
|
|
&& p.getActions().matches(".*resolve.*")){
|
|
throw new SecurityException(
|
|
"DOH: liveconnect resolve locks up Safari 3. Denying resolve request.");
|
|
}else if(p.equals(new java.awt.AWTPermission("watchMousePointer"))){
|
|
// enable robot to watch mouse
|
|
}else{
|
|
oldsecurity.checkPermission(p);
|
|
}
|
|
}
|
|
|
|
public void checkPermission(Permission perm, Object context){
|
|
checkPermission(perm);
|
|
}
|
|
}
|
|
|
|
public void setClipboardText(double sec, final String data) {
|
|
if(!isSecure(sec))
|
|
return;
|
|
// called by doh.robot.setClipboard
|
|
// see it for details
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
StringSelection ss = new StringSelection(data);
|
|
getSystemClipboard().setContents(ss, ss);
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
|
|
public void setClipboardHtml(double sec, final String data) {
|
|
if(!isSecure(sec))
|
|
return;
|
|
// called by doh.robot.setClipboard when format=='text/html'
|
|
// see it for details
|
|
AccessController.doPrivileged(new PrivilegedAction(){
|
|
public Object run(){
|
|
String mimeType = "text/html;class=java.lang.String";//type + "; charset=" + charset;// + "; class=" + transferType;
|
|
TextTransferable transferable = new TextTransferable(mimeType, data);
|
|
getSystemClipboard().setContents(transferable, transferable);
|
|
return null;
|
|
}
|
|
});
|
|
}
|
|
private static java.awt.datatransfer.Clipboard getSystemClipboard() {
|
|
return toolkit.getSystemClipboard();
|
|
}
|
|
|
|
private static class TextTransferable implements Transferable, ClipboardOwner {
|
|
private String data;
|
|
private static ArrayList htmlFlavors = new ArrayList();
|
|
|
|
static{
|
|
try{
|
|
htmlFlavors.add(new DataFlavor("text/plain;charset=UTF-8;class=java.lang.String"));
|
|
htmlFlavors.add(new DataFlavor("text/html;charset=UTF-8;class=java.lang.String"));
|
|
}catch(ClassNotFoundException ex){
|
|
ex.printStackTrace();
|
|
}
|
|
}
|
|
|
|
|
|
public TextTransferable(String mimeType, String data){
|
|
this.data = data;
|
|
}
|
|
|
|
public DataFlavor[] getTransferDataFlavors(){
|
|
return (DataFlavor[]) htmlFlavors.toArray(new DataFlavor[htmlFlavors.size()]);
|
|
}
|
|
|
|
public boolean isDataFlavorSupported(DataFlavor flavor){
|
|
return htmlFlavors.contains(flavor);
|
|
}
|
|
|
|
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException{
|
|
if (String.class.equals(flavor.getRepresentationClass())){
|
|
return data;
|
|
}
|
|
|
|
throw new UnsupportedFlavorException(flavor);
|
|
|
|
}
|
|
|
|
public void lostOwnership(java.awt.datatransfer.Clipboard clipboard, Transferable contents){
|
|
data = null;
|
|
}
|
|
}
|
|
}
|