Package

Source Code of DOHRobot$TextTransferable

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;
    }
  }
}
TOP

Related Classes of DOHRobot$TextTransferable

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.