source: Dev/branches/rest-dojo-ui/client/util/doh/robot/DOHRobot.java @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 52.1 KB
Line 
1import java.security.*;
2import java.applet.Applet;
3import java.awt.*;
4import java.util.*;
5import java.util.concurrent.*;
6import java.awt.event.*;
7import netscape.javascript.*;
8import java.io.*;
9import java.lang.reflect.*;
10import java.net.URL;
11import java.awt.datatransfer.*;
12import javax.swing.JOptionPane;
13import javax.swing.JDialog;
14import java.awt.image.*;
15
16public final class DOHRobot extends Applet{
17        // order of execution:
18        // wait for user to trust applet
19        // load security manager to prevent Safari hang
20        // discover document root in screen coordinates
21        // discover keyboard capabilities
22        // tell doh to continue with the test
23
24        // link to doh
25        // To invoke doh, call eval with window.eval("jsexp")
26        // Note that the "window" is an iframe!
27        // You might need to break out of the iframe with an intermediate function
28        // in the parent window.
29        private JSObject window = null;
30
31        // java.awt.Robot
32        // drives the test
33        // you need to sign the applet JAR for this to work
34        private Robot robot = null;
35
36        // In order to preserve the execution order of Robot commands,
37        // we have to serialize commands by having them join() the previous one.
38        // Otherwise, if you run doh.robot.typeKeys("dijit"), you frequently get something
39        // like "diijt"
40        //private static Thread previousThread = null;
41       
42        private static ExecutorService threadPool = null;
43
44        // Keyboard discovery.
45        // At init, the Robot types keys into a textbox and JavaScript tells the
46        // Robot what it got back.
47        // charMap maps characters to the KeyEvent that generates the character on
48        // the user's machine.
49        // charMap uses the Java 1.4.2 (lack of) template syntax for wider
50        // compatibility.
51        private static HashMap charMap = null;
52        // Java key constants to iterate over
53        // not all are available on all machines!
54        private Vector vkKeys = null;
55        // some state variables
56        private boolean shift = false;
57        private boolean altgraph = false;
58        private boolean ctrl = false;
59        private boolean alt = false;
60        private boolean meta = false;
61        private boolean numlockDisabled = false;
62        private long timingError = 0; // how much time the last robot call was off by
63        // shake hands with JavaScript the first keypess to wake up FF2/Mac
64        private boolean jsready = false;
65        private String keystring = "";
66
67        // Firebug gets a little too curious about our applet for its own good
68        // setting firebugIgnore to true ensures Firebug doesn't break the applet
69        public boolean firebugIgnore = true;
70
71        private static String os=System.getProperty("os.name").toUpperCase();
72        private static Toolkit toolkit=Toolkit.getDefaultToolkit();
73       
74        private SecurityManager securitymanager;
75        private double key = -1;
76
77        // The screen x,y of the document upper left corner.
78        // We only set it once so people are less likely to take it over.
79        private boolean inited = false;
80        private int docScreenX = -100;
81        private int docScreenY = -100;
82        private int docScreenXMax;
83        private int docScreenYMax;
84        private Point margin = null;
85        private boolean mouseSecurity = false;
86
87        // The last reported mouse x,y.
88        // If this is different from the real one, something's up.
89        private int lastMouseX;
90        private int lastMouseY;
91        public int dir=1;
92
93        // save a pointer to doh.robot for fast access
94        JSObject dohrobot = null;
95
96        // trackingImage to visually track robot down
97        private BufferedImage trackingImage;
98        Point locationOnScreen = null;
99
100        // java.awt.Applet methods
101        public void stop(){
102                window = null;
103                dohrobot = null;
104                // only secure code run once
105                if(key != -2){
106                        // prevent further execution of secure functions
107                        key = -2;
108                        // Java calls this when you close the window.
109                        // It plays nice and restores the old security manager.
110                        AccessController.doPrivileged(new PrivilegedAction(){
111                                public Object run(){
112                                        if(threadPool!=null){
113                                                threadPool.shutdownNow();
114                                        }
115                                        log("Stop");
116                                        securitymanager.checkTopLevelWindow(null);
117                                        log("Security manager reset");
118                                        return null;
119                                }
120                        });
121                }
122        }
123
124        final private class onvisible extends ComponentAdapter{
125                public void componentShown(ComponentEvent evt){
126                        // sets the security manager to fix a bug in liveconnect in Safari on Mac
127                        if(key != -1){ return; }
128                        Thread thread = new Thread(){
129                                public void run(){
130                                        log("Document root: ~"+applet().getLocationOnScreen().toString());
131                                        window = (JSObject) JSObject.getWindow(applet());   
132                                        AccessController.doPrivileged(new PrivilegedAction(){
133                                                public Object run(){
134                                                        log("> init Robot");
135                                                        try{
136                                                                SecurityManager oldsecurity = System.getSecurityManager();
137                                                                boolean needsSecurityManager = applet().getParameter("needsSecurityManager").equals("true");
138                                                                log("Socket connections managed? "+needsSecurityManager);
139                                                                try{
140                                                                        securitymanager = oldsecurity;
141                                                                        securitymanager.checkTopLevelWindow(null);
142                                                                        // xdomain
143                                                                        if(charMap == null){
144                                                                                if(!confirm("DOH has detected that the current Web page is attempting to access DOH,\n"+
145                                                                                                        "but belongs to a different domain than the one you agreed to let DOH automate.\n"+
146                                                                                                        "If you did not intend to start a new DOH test by visiting this Web page,\n"+
147                                                                                                        "press Cancel now and leave the Web page.\n"+
148                                                                                                        "Otherwise, press OK to trust this domain to automate DOH tests.")){
149                                                                                        stop();
150                                                                                        return null;
151                                                                                }
152                                                                        }
153                                                                        log("Found old security manager");
154                                                                }catch(Exception e){
155                                                                        log("Making new security manager");
156                                                                        securitymanager = new RobotSecurityManager(needsSecurityManager,
157                                                                                        oldsecurity);
158                                                                        securitymanager.checkTopLevelWindow(null);
159                                                                        System.setSecurityManager(securitymanager);
160                                                                }
161                                                        }catch(Exception e){
162                                                                log("Error calling _init_: "+e.getMessage());
163                                                                key = -2;
164                                                                e.printStackTrace();
165                                                        }
166                                                        log("< init Robot");
167                                                        return null;
168                                                }
169                                        });
170                                        if(key == -2){
171                                                // applet not trusted
172                                                // start the test without it
173                                                window.eval("doh.robot._appletDead=true;doh.run();");
174                                        }else{
175                                                // now that the applet has really started, let doh know it's ok to use it
176                                                log("_initRobot");
177                                                try{
178                                                        dohrobot = (JSObject) window.eval("doh.robot");
179                                                        dohrobot.call("_initRobot", new Object[]{ applet() });
180                                                }catch(Exception e){
181                                                        e.printStackTrace();
182                                                }
183                                        }
184                                }
185                        };
186                        threadPool.execute(thread);
187                }
188        }
189
190        public void init(){
191                threadPool = Executors.newFixedThreadPool(1);
192                // ensure isShowing = true
193                addComponentListener(new onvisible());
194                ProfilingThread jitProfile=new ProfilingThread ();
195                jitProfile.startProfiling();
196                jitProfile.endProfiling();
197                trackingImage=new BufferedImage(3,3,BufferedImage.TYPE_INT_RGB);
198                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);
199        }
200
201        // loading functions
202        public void _setKey(double key){
203                if(key == -1){
204                        return;
205                }else if(this.key == -1){
206                        this.key = key;
207                }
208        }
209
210        protected Point getDesktopMousePosition() throws Exception{
211                Class mouseInfoClass;
212                Class pointerInfoClass;
213                mouseInfoClass = Class.forName("java.awt.MouseInfo");
214                pointerInfoClass = Class.forName("java.awt.PointerInfo");
215                Method getPointerInfo = mouseInfoClass.getMethod("getPointerInfo", new Class[0]);
216                Method getLocation = pointerInfoClass.getMethod("getLocation", new Class[0]);
217                Object pointer=null;
218                try{
219                        pointer = getPointerInfo.invoke(pointerInfoClass,new Object[0]);
220                }catch(java.lang.reflect.InvocationTargetException e){
221                        e.getTargetException().printStackTrace();
222                }
223                return (Point)(getLocation.invoke(pointer,new Object[0]));
224        }
225       
226        public Point getLocationOnScreen(){
227                return locationOnScreen==null? super.getLocationOnScreen(): locationOnScreen;
228        }
229       
230        private boolean mouseSecure() throws Exception{
231                // Use MouseInfo to ensure that mouse is inside browser.
232                // Only works in Java 1.5, but the DOHRobot must compile for 1.4.
233                if(!mouseSecurity){ return true; }
234                Point mousePosition=null;
235                try{
236                        mousePosition=getDesktopMousePosition();
237                }catch(Exception e){
238                        return true;
239                }
240                return mousePosition.x >= docScreenX
241                        && mousePosition.x <= docScreenXMax
242                        && mousePosition.y >= docScreenY
243                        && mousePosition.y <= docScreenYMax;
244        }
245
246        private boolean isSecure(double key){
247                boolean result = this.key != -1 && this.key != -2 && this.key == key;
248                try{
249                        result=result&&mouseSecure();
250                }catch(Exception e){
251                        e.printStackTrace();
252                        result=false;
253                }
254                if(!result&&this.key!=-2){
255                        this.key=-2;
256                        window.eval("doh.robot._appletDead=true;");
257                        log("User aborted test; mouse moved off of browser");
258                        alert("User aborted test; mouse moved off of browser.");
259                }
260                log("Key secure: " + result);
261                return result;
262        }
263
264        public void _callLoaded(final double sec){
265                log("> _callLoaded Robot");
266                Runnable thread = new Runnable(){
267                        public void run(){
268                                if(!isSecure(sec)){
269                                        return;
270                                }
271                                AccessController.doPrivileged(new PrivilegedAction(){
272                                        public Object run(){
273                                                Point p = getLocationOnScreen();
274                                                if(os.indexOf("MAC") != -1){
275                                                        // Work around stupid Apple OS X bug affecting Safari 5.1 and FF4.
276                                                        // Seems to have to do with the plugin they compile with rather than the jvm itself because Safari5.0 and FF3.6 still work.
277                                                        p = new Point();
278                                                        int screen=0;
279                                                        int minscreen=-1;
280                                                        int mindifference=Integer.MAX_VALUE;
281                                                        GraphicsDevice[] screens=GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
282                                                        try{
283                                                                for(screen=0; screen<screens.length; screen++){
284                                                                        // take picture
285                                                                        DisplayMode mode=screens[screen].getDisplayMode();
286                                                                        int width=mode.getWidth();
287                                                                        int height=mode.getHeight();
288                                                                        int twidth=trackingImage.getWidth();
289                                                                        int theight=trackingImage.getHeight();
290                                                                        Robot screenshooter=new Robot(screens[screen]);
291                                                                        log("screen dimensions: "+width+" "+height);
292                                                                        BufferedImage screenshot=screenshooter.createScreenCapture(new Rectangle(0,0,width,height));
293                                                                        // Ideally (in Windows) we would now slide trackingImage until we find an identical match inside screenshot.
294                                                                        // Unfortunately because the Mac (what we are trying to fix) does terrible, awful things to graphics it displays,
295                                                                        // we will need to find the "most similar" (smallest difference in pixels) square and click there.
296                                                                        int x=0,y=0;
297                                                                        for(x=0; x<=width-twidth; x++){
298                                                                                for(y=0; y<=height-theight; y++){
299                                                                                        int count=0;
300                                                                                        int difference=0;
301                                                                                        scanImage:
302                                                                                        for(int x2=0; x2<twidth; x2++){
303                                                                                                for(int y2=0; y2<theight; y2++){
304                                                                                                        int rgbdiff=Math.abs(screenshot.getRGB(x+x2,y+y2)-trackingImage.getRGB(x2,y2));
305                                                                                                        difference=difference+rgbdiff;
306                                                                                                        // short circuit mismatches
307                                                                                                        if(difference>=mindifference){
308                                                                                                                break scanImage;
309                                                                                                        }
310                                                                                                }
311                                                                                        }
312                                                                                        if(difference<mindifference){
313                                                                                                p.x=x;
314                                                                                                p.y=y;
315                                                                                                mindifference=difference;
316                                                                                                minscreen=screen;
317                                                                                        }
318                                                                                }
319                                                                        }
320                                                                }
321                                                                // create temp robot to put mouse in right spot
322                                                                robot=new Robot(screens[minscreen]);
323                                                                robot.setAutoWaitForIdle(true);
324                                                        }catch(Exception e){
325                                                                e.printStackTrace();
326                                                        }
327                                                        if(p.x==0&&p.y==0){
328                                                                // shouldn't happen...
329                                                                throw new RuntimeException("Robot not found on screen");
330                                                        }
331                                                        locationOnScreen=p;
332                                                }else{
333                                                        // create default temp robot that should work on non-Macs
334                                                        try{
335                                                                robot=new Robot();
336                                                                robot.setAutoWaitForIdle(true);
337                                                        }catch(Exception e){}
338                                                }
339                                                log("Document root: ~"+p.toString());
340                                                int x = p.x + 16;
341                                                int y = p.y + 8;
342                                                // click the mouse over the text box
343                                                try{
344                                                        Thread.sleep(100);
345                                                }catch(Exception e){};
346                                                robot.mouseMove(x, y);
347                                                try{
348                                                        // mouse in right spot; restore control to original robot using browser's preferred coordinates
349                                                        robot = new Robot();
350                                                        robot.setAutoWaitForIdle(true);
351                                                        Thread.sleep(100);
352                                                }catch(Exception e){};
353                                                robot.mousePress(InputEvent.BUTTON1_MASK);
354                                                try{
355                                                        Thread.sleep(100);
356                                                }catch(Exception e){}
357                                                robot.mouseRelease(InputEvent.BUTTON1_MASK);
358                                                try{
359                                                        Thread.sleep(100);
360                                                }catch(Exception e){}
361                                                log("< _callLoaded Robot");
362                                                return null;
363                                        }
364                                });
365                        }
366                };
367                threadPool.execute(thread);
368        }
369
370        // convenience functions
371        private DOHRobot applet(){
372                return this;
373        }
374
375        public void log(final String s){
376                AccessController.doPrivileged(new PrivilegedAction(){
377                        public Object run(){
378                                System.out.println((new Date()).toString() + ": " + s);
379                                return null;
380                        }
381                });
382        }
383
384        private void alert(final String s){
385                AccessController.doPrivileged(new PrivilegedAction(){
386                        public Object run(){
387                                window.eval("top.alert(\"" + s + "\");");
388                                return null;
389                        }
390                });
391        }
392
393        private boolean confirm(final String s){
394                // show a Java confirm dialog.
395                // Mac seems to lock up when showing a JS confirm from Java.
396                //return JOptionPane.showConfirmDialog(this, s, "doh.robot", JOptionPane.OK_CANCEL_OPTION)==JOptionPane.OK_OPTION);
397                JOptionPane pane = new JOptionPane(s, JOptionPane.DEFAULT_OPTION, JOptionPane.OK_CANCEL_OPTION);
398                JDialog dialog = pane.createDialog(this, "doh.robot");
399                dialog.setLocationRelativeTo(this);
400                dialog.show();
401                return ((Integer)pane.getValue()).intValue()==JOptionPane.OK_OPTION;
402        }
403
404        // mouse discovery code
405        public void setDocumentBounds(final double sec, int x, int y, int w, int h) throws Exception{
406                // call from JavaScript
407                // tells the Robot where the screen x,y of the upper left corner of the
408                // document are
409                // not screenX/Y of the window; really screenLeft/Top in IE, but not all
410                // browsers have this
411                log("> setDocumentBounds");
412                if(!isSecure(sec))
413                        return;
414                if(!inited){
415                        inited = true;
416                        this.lastMouseX = this.docScreenX = x;
417                        this.lastMouseY = this.docScreenY = y;
418                        this.docScreenXMax = x + w;
419                        this.docScreenYMax = y + h;
420                        // compute difference between position and browser edge for future reference
421                        this.margin = getLocationOnScreen();
422                        this.margin.x -= x;
423                        this.margin.y -= y;
424                        mouseSecurity=true;
425                }
426                log("< setDocumentBounds");
427        }
428
429        // keyboard discovery code
430        private void _mapKey(char charCode, int keyindex, boolean shift,
431                        boolean altgraph){
432                log("_mapKey: " + charCode);
433                // if character is not in map, add it
434                if(!charMap.containsKey(new Integer(charCode))){
435                        log("Notified: " + (char) charCode);
436                        KeyEvent event = new KeyEvent(applet(), 0, 0,
437                                        (shift ? KeyEvent.SHIFT_MASK : 0)
438                                                        + (altgraph ? KeyEvent.ALT_GRAPH_MASK : 0),
439                                        ((Integer) vkKeys.get(keyindex)).intValue(),
440                                        (char) charCode);
441                        charMap.put(new Integer(charCode), event);
442                        log("Mapped char " + (char) charCode + " to KeyEvent " + event);
443                        if(((char) charCode) >= 'a' && ((char) charCode) <= 'z'){
444                                // put shifted version of a-z in automatically
445                                int uppercharCode = (int) Character
446                                                .toUpperCase((char) charCode);
447                                event = new KeyEvent(applet(), 0, 0, KeyEvent.SHIFT_MASK
448                                                + (altgraph ? KeyEvent.ALT_GRAPH_MASK : 0),
449                                                ((Integer) vkKeys.get(keyindex)).intValue(),
450                                                (char) uppercharCode);
451                                charMap.put(new Integer(uppercharCode), event);
452                                log("Mapped char " + (char) uppercharCode + " to KeyEvent "
453                                                + event);
454                        }
455                }
456        }
457
458        public void _notified(final double sec, final String chars){
459                // decouple from JavaScript; thread join could hang it
460                Runnable thread = new Runnable(){
461                        public void run(){
462                                if(!isSecure(sec))
463                                        return;
464                                AccessController.doPrivileged(new PrivilegedAction(){
465                                        public Object run(){
466                                                keystring += chars;
467                                                if(altgraph && !shift){
468                                                        shift = false;
469                                                        // Set robot auto delay now that FF/Mac inited all of the keys.
470                                                        // Good for DND.
471                                                        robot.setAutoDelay(1);
472                                                        try{
473                                                                log(keystring);
474                                                                int index = 0;
475                                                                for (int i = 0; (i < vkKeys.size())
476                                                                                && (index < keystring.length()); i++){
477                                                                        char c = keystring.charAt(index++);
478                                                                        _mapKey(c, i, false, false);
479                                                                }
480                                                                for (int i = 0; (i < vkKeys.size())
481                                                                                && (index < keystring.length()); i++){
482                                                                        char c = keystring.charAt(index++);
483                                                                        _mapKey(c, i, true, false);
484                                                                }
485                                                                for (int i = 0; (i < vkKeys.size())
486                                                                                && (index < keystring.length()); i++){
487                                                                        char c = keystring.charAt(index++);
488                                                                        _mapKey(c, i, false, true);
489                                                                }
490                                                                // notify DOH that the applet finished init
491                                                                dohrobot.call("_onKeyboard", new Object[]{});
492                                                        }catch(Exception e){
493                                                                e.printStackTrace();
494                                                        }
495                                                        return null;
496                                                }else if(!shift){
497                                                        shift = true;
498                                                }else{
499                                                        shift = false;
500                                                        altgraph = true;
501                                                }
502                                                pressNext();
503                                                // }
504                                                return null;
505                                        }
506                                });
507                        }
508                };
509                threadPool.execute(thread);
510        }
511
512        private void pressNext(){
513                Runnable thread = new Runnable(){
514                        public void run(){
515                                // first time, press shift (have to do it here instead of
516                                // _notified to avoid IllegalThreadStateException on Mac)
517                                log("starting up, " + shift + " " + altgraph);
518                                if(shift){
519                                        robot.keyPress(KeyEvent.VK_SHIFT);
520                                        log("Pressing shift");
521                                }
522                                try{
523                                        if(altgraph){
524                                                robot.keyPress(KeyEvent.VK_ALT_GRAPH);
525                                                log("Pressing alt graph");
526                                        }
527                                }catch(Exception e){
528                                        log("Error pressing alt graph");
529                                        e.printStackTrace();
530                                        _notified(key, "");
531                                        return;
532                                }
533                                dohrobot.call("_nextKeyGroup", new Object[]{ new Integer(vkKeys.size()) });
534                                for (int keyindex = 0; keyindex < vkKeys.size(); keyindex++){
535                                        try{
536                                                log("Press "
537                                                                + ((Integer) vkKeys.get(keyindex)).intValue());
538                                                robot.keyPress(((Integer) vkKeys.get(keyindex))
539                                                                .intValue());
540                                                log("Release "
541                                                                + ((Integer) vkKeys.get(keyindex)).intValue());
542                                                robot.keyRelease(((Integer) vkKeys.get(keyindex))
543                                                                .intValue());
544                                                if(altgraph && (keyindex == (vkKeys.size() - 1))){
545                                                        robot.keyRelease(KeyEvent.VK_ALT_GRAPH);
546                                                        log("Releasing alt graph");
547                                                }
548                                                if(shift && (keyindex == (vkKeys.size() - 1))){
549                                                        robot.keyRelease(KeyEvent.VK_SHIFT);
550                                                        log("Releasing shift");
551                                                }
552                                        }catch(Exception e){
553                                        }
554                                        try{
555                                                log("Press space");
556                                                robot.keyPress(KeyEvent.VK_SPACE);
557                                                log("Release space");
558                                                robot.keyRelease(KeyEvent.VK_SPACE);
559                                        }catch(Exception e){
560                                                e.printStackTrace();
561                                        }
562                                }
563                        }
564                };
565                threadPool.execute(thread);
566        }
567
568        public void _initWheel(final double sec){
569                log("> initWheel");
570                Runnable thread=new Runnable(){
571                        public void run(){
572                                if(!isSecure(sec))
573                                        return;
574                                Thread.yield();
575                                // calibrate the mouse wheel now that textbox is focused
576                                dir=1;
577                                // fixed in 10.6.2 update 1 and 10.5.8 update 6:
578                                // http://developer.apple.com/mac/library/releasenotes/CrossPlatform/JavaSnowLeopardUpdate1LeopardUpdate6RN/ResolvedIssues/ResolvedIssues.html
579                                // Radar #6193836
580                                if(os.indexOf("MAC") != -1){
581                                        // see if the version is greater than 10.5.8
582                                        String[] sfixedVersion = "10.5.8".split("\\.");
583                                        int[] fixedVersion = new int[3];
584                                        String[] sthisVersion = System.getProperty("os.version").split("\\.");
585                                        int[] thisVersion = new int[3];
586                                        for(int i=0; i<3; i++){
587                                                fixedVersion[i]=Integer.valueOf(sfixedVersion[i]).intValue();
588                                                thisVersion[i]=Integer.valueOf(sthisVersion[i]).intValue();
589                                        };
590                                        // 10.5.8, the fix level, should count as fixed
591                                        // on the other hand, 10.6.0 and 10.6.1 should not
592                                        boolean isFixed = !System.getProperty("os.version").equals("10.6.0")&&!System.getProperty("os.version").equals("10.6.1");
593                                        for(int i=0; i<fixedVersion.length&&isFixed; i++){
594                                                if(thisVersion[i]>fixedVersion[i]){
595                                                        // definitely newer at this point
596                                                        isFixed = true;
597                                                        break;
598                                                }else if(thisVersion[i]<fixedVersion[i]){
599                                                        // definitely older
600                                                        isFixed = false;
601                                                        break;
602                                                }
603                                                // equal; continue to next dot
604
605                                        }
606                                        // flip dir if not fixed
607                                        dir=isFixed?dir:-dir;
608                                }
609                                robot.mouseWheel(dir);
610                                try{
611                                        Thread.sleep(100);
612                                }catch(Exception e){}
613                                log("< initWheel");
614                        }
615                };
616                threadPool.execute(thread);
617        }
618
619        public void _initKeyboard(final double sec){
620                log("> initKeyboard");
621                // javascript entry point to discover the keyboard
622                if(charMap != null){
623                        dohrobot.call("_onKeyboard", new Object[]{});
624                        return;
625                }
626                Runnable thread = new Runnable(){
627                        public void run(){
628                                if(!isSecure(sec))
629                                        return;
630                                AccessController.doPrivileged(new PrivilegedAction(){
631                                        public Object run(){
632                                                charMap = new HashMap();
633                                                KeyEvent event = new KeyEvent(applet(), 0, 0, 0,
634                                                                KeyEvent.VK_SPACE, ' ');
635                                                charMap.put(new Integer(32), event);
636                                                try{
637                                                        // a-zA-Z0-9 + 29 others
638                                                        vkKeys = new Vector();
639                                                        for (char i = 'a'; i <= 'z'; i++){
640                                                                vkKeys.add(new Integer(KeyEvent.class.getField(
641                                                                                "VK_" + Character.toUpperCase((char) i))
642                                                                                .getInt(null)));
643                                                        }
644                                                        for (char i = '0'; i <= '9'; i++){
645                                                                vkKeys.add(new Integer(KeyEvent.class.getField(
646                                                                                "VK_" + Character.toUpperCase((char) i))
647                                                                                .getInt(null)));
648                                                        }
649                                                        int[] mykeys = new int[]{ KeyEvent.VK_COMMA,
650                                                                        KeyEvent.VK_MINUS, KeyEvent.VK_PERIOD,
651                                                                        KeyEvent.VK_SLASH, KeyEvent.VK_SEMICOLON,
652                                                                        KeyEvent.VK_LEFT_PARENTHESIS,
653                                                                        KeyEvent.VK_NUMBER_SIGN, KeyEvent.VK_PLUS,
654                                                                        KeyEvent.VK_RIGHT_PARENTHESIS,
655                                                                        KeyEvent.VK_UNDERSCORE,
656                                                                        KeyEvent.VK_EXCLAMATION_MARK, KeyEvent.VK_DOLLAR,
657                                                                        KeyEvent.VK_CIRCUMFLEX, KeyEvent.VK_AMPERSAND,
658                                                                        KeyEvent.VK_ASTERISK, KeyEvent.VK_QUOTEDBL,
659                                                                        KeyEvent.VK_LESS, KeyEvent.VK_GREATER,
660                                                                        KeyEvent.VK_BRACELEFT, KeyEvent.VK_BRACERIGHT,
661                                                                        KeyEvent.VK_COLON, KeyEvent.VK_BACK_QUOTE,
662                                                                        KeyEvent.VK_QUOTE, KeyEvent.VK_OPEN_BRACKET,
663                                                                        KeyEvent.VK_BACK_SLASH, KeyEvent.VK_CLOSE_BRACKET,
664                                                                        KeyEvent.VK_EQUALS };
665                                                        for (int i = 0; i < mykeys.length; i++){
666                                                                vkKeys.add(new Integer(mykeys[i]));
667                                                        }
668                                                }catch(Exception e){
669                                                        e.printStackTrace();
670                                                }
671                                                robot.setAutoDelay(1);
672                                                // prime the event pump for Google Chome - so fast it doesn't even stop to listen for key events!
673                                                // send spaces until JS says to stop
674                                                int count=0;
675                                                boolean waitingOnSpace = true;
676                                                do{
677                                                        log("Pressed space");
678                                                        robot.keyPress(KeyEvent.VK_SPACE);
679                                                        robot.keyRelease(KeyEvent.VK_SPACE);
680                                                        count++;
681                                                        waitingOnSpace = ((Boolean)window.eval("doh.robot._spaceReceived")).equals(Boolean.FALSE);
682                                                        log("JS still waiting on a space? "+waitingOnSpace);
683                                                }while(count<500&&waitingOnSpace);
684                                                robot.keyPress(KeyEvent.VK_ENTER);
685                                                robot.keyRelease(KeyEvent.VK_ENTER);
686                                                robot.setAutoDelay(0);
687                                                log("< initKeyboard");
688                                                pressNext();
689                                                return null;
690                                        }
691                                });
692                        }
693                };
694                threadPool.execute(thread);
695        }
696
697        public void typeKey(double sec, final int charCode, final int keyCode,
698                        final boolean alt, final boolean ctrl, final boolean shift, final boolean meta,
699                        final int delay, final boolean async){
700                if(!isSecure(sec))
701                        return;
702                // called by doh.robot._keyPress
703                // see it for details
704                AccessController.doPrivileged(new PrivilegedAction(){
705                        public Object run(){
706                                try{
707                                        log("> typeKey Robot " + charCode + ", " + keyCode + ", " + async);
708                                        KeyPressThread thread = new KeyPressThread(charCode,
709                                                        keyCode, alt, ctrl, shift, meta, delay);
710                                        if(async){
711                                                Thread asyncthread=new Thread(thread);
712                                                asyncthread.start();
713                                        }else{
714                                                threadPool.execute(thread);
715                                        }
716                                        log("< typeKey Robot");
717                                }catch(Exception e){
718                                        log("Error calling typeKey");
719                                        e.printStackTrace();
720                                }
721                                return null;
722                        }
723                });
724        }
725
726        public void upKey(double sec, final int charCode, final int keyCode, final int delay){
727                // called by doh.robot.keyDown
728                // see it for details
729                // a nice name like "keyUp" is reserved in Java
730                if(!isSecure(sec))
731                        return;
732                AccessController.doPrivileged(new PrivilegedAction(){
733                        public Object run(){
734                                log("> upKey Robot " + charCode + ", " + keyCode);
735                                KeyUpThread thread = new KeyUpThread(charCode, keyCode, delay);
736                                threadPool.execute(thread);
737                                log("< upKey Robot");
738                                return null;
739                        }
740                });
741        }
742
743        public void downKey(double sec, final int charCode, final int keyCode, final int delay){
744                // called by doh.robot.keyUp
745                // see it for details
746                // a nice name like "keyDown" is reserved in Java
747                if(!isSecure(sec))
748                        return;
749                AccessController.doPrivileged(new PrivilegedAction(){
750                        public Object run(){
751                                log("> downKey Robot " + charCode + ", " + keyCode);
752                                KeyDownThread thread = new KeyDownThread(charCode, keyCode, delay);
753                                threadPool.execute(thread);
754                                log("< downKey Robot");
755                                return null;
756                        }
757                });
758        }
759
760        public void pressMouse(double sec, final boolean left,
761                        final boolean middle, final boolean right, final int delay){
762                if(!isSecure(sec))
763                        return;
764                // called by doh.robot.mousePress
765                // see it for details
766                // a nice name like "mousePress" is reserved in Java
767                AccessController.doPrivileged(new PrivilegedAction(){
768                        public Object run(){
769                                log("> mousePress Robot " + left + ", " + middle + ", " + right);
770                                MousePressThread thread = new MousePressThread(
771                                                (left ? InputEvent.BUTTON1_MASK : 0)
772                                                                + (middle ? InputEvent.BUTTON2_MASK : 0)
773                                                                + (right ? InputEvent.BUTTON3_MASK : 0), delay);
774                                threadPool.execute(thread);
775                                log("< mousePress Robot");
776                                return null;
777                        }
778                });
779        }
780
781        public void releaseMouse(double sec, final boolean left,
782                        final boolean middle, final boolean right, final int delay){
783                if(!isSecure(sec))
784                        return;
785                // called by doh.robot.mouseRelease
786                // see it for details
787                // a nice name like "mouseRelease" is reserved in Java
788                AccessController.doPrivileged(new PrivilegedAction(){
789                        public Object run(){
790                                log("> mouseRelease Robot " + left + ", " + middle + ", "
791                                                + right);
792                                MouseReleaseThread thread = new MouseReleaseThread(
793                                                (left ? InputEvent.BUTTON1_MASK : 0)
794                                                                + (middle ? InputEvent.BUTTON2_MASK : 0)
795                                                                + (right ? InputEvent.BUTTON3_MASK : 0), delay
796                                                );
797                                threadPool.execute(thread);
798                                log("< mouseRelease Robot");
799                                return null;
800                        }
801                });
802        }
803
804        protected boolean destinationInView(int x, int y){
805                return !(x > docScreenXMax || y > docScreenYMax || x < docScreenX || y < docScreenY);
806        }
807       
808        public void moveMouse(double sec, final int x1, final int y1, final int d, final int duration){
809                // called by doh.robot.mouseMove
810                // see it for details
811                // a nice name like "mouseMove" is reserved in Java
812                if(!isSecure(sec))
813                        return;
814                AccessController.doPrivileged(new PrivilegedAction(){
815                        public Object run(){
816                                int x = x1 + docScreenX;
817                                int y = y1 + docScreenY;
818                                if(!destinationInView(x,y)){
819                                        // TODO: try to scroll view
820                                        log("Request to mouseMove denied");
821                                        return null;
822                                }
823                                int delay = d;
824                                log("> mouseMove Robot " + x + ", " + y);
825                                MouseMoveThread thread = new MouseMoveThread(x, y, delay,
826                                                duration);
827                                threadPool.execute(thread);
828                                log("< mouseMove Robot");
829                                return null;
830                        }
831                });
832        }
833
834        public void wheelMouse(double sec, final int amount, final int delay, final int duration){
835                // called by doh.robot.mouseWheel
836                // see it for details
837                if(!isSecure(sec))
838                        return;
839                AccessController.doPrivileged(new PrivilegedAction(){
840                        public Object run(){
841                                MouseWheelThread thread = new MouseWheelThread(amount, delay, duration);
842                                threadPool.execute(thread);
843                                return null;
844                        }
845                });
846        }
847
848        private int getVKCode(int charCode, int keyCode){
849                int keyboardCode = 0;
850                if(charCode >= 32){
851                        // if it is printable, then it lives in our hashmap
852                        KeyEvent event = (KeyEvent) charMap.get(new Integer(charCode));
853                        keyboardCode = event.getKeyCode();
854                }
855                else{
856                        switch (keyCode){
857                                case 13:
858                                        keyboardCode = KeyEvent.VK_ENTER;
859                                        break;
860                                case 8:
861                                        keyboardCode = KeyEvent.VK_BACK_SPACE;
862                                        break;
863                                case 25:// shift tab for Safari
864                                case 9:
865                                        keyboardCode = KeyEvent.VK_TAB;
866                                        break;
867                                case 12:
868                                        keyboardCode = KeyEvent.VK_CLEAR;
869                                        break;
870                                case 16:
871                                        keyboardCode = KeyEvent.VK_SHIFT;
872                                        break;
873                                case 17:
874                                        keyboardCode = KeyEvent.VK_CONTROL;
875                                        break;
876                                case 18:
877                                        keyboardCode = KeyEvent.VK_ALT;
878                                        break;
879                                case 63250:
880                                case 19:
881                                        keyboardCode = KeyEvent.VK_PAUSE;
882                                        break;
883                                case 20:
884                                        keyboardCode = KeyEvent.VK_CAPS_LOCK;
885                                        break;
886                                case 27:
887                                        keyboardCode = KeyEvent.VK_ESCAPE;
888                                        break;
889                                case 32:
890                                        log("it's a space");
891                                        keyboardCode = KeyEvent.VK_SPACE;
892                                        break;
893                                case 63276:
894                                case 33:
895                                        keyboardCode = KeyEvent.VK_PAGE_UP;
896                                        break;
897                                case 63277:
898                                case 34:
899                                        keyboardCode = KeyEvent.VK_PAGE_DOWN;
900                                        break;
901                                case 63275:
902                                case 35:
903                                        keyboardCode = KeyEvent.VK_END;
904                                        break;
905                                case 63273:
906                                case 36:
907                                        keyboardCode = KeyEvent.VK_HOME;
908                                        break;
909
910                                /**
911                                 * Constant for the <b>left</b> arrow key.
912                                 */
913                                case 63234:
914                                case 37:
915                                        keyboardCode = KeyEvent.VK_LEFT;
916                                        break;
917
918                                /**
919                                 * Constant for the <b>up</b> arrow key.
920                                 */
921                                case 63232:
922                                case 38:
923                                        keyboardCode = KeyEvent.VK_UP;
924                                        break;
925
926                                /**
927                                 * Constant for the <b>right</b> arrow key.
928                                 */
929                                case 63235:
930                                case 39:
931                                        keyboardCode = KeyEvent.VK_RIGHT;
932                                        break;
933
934                                /**
935                                 * Constant for the <b>down</b> arrow key.
936                                 */
937                                case 63233:
938                                case 40:
939                                        keyboardCode = KeyEvent.VK_DOWN;
940                                        break;
941                                case 63272:
942                                case 46:
943                                        keyboardCode = KeyEvent.VK_DELETE;
944                                        break;
945                                case 224:
946                                case 91:
947                                        keyboardCode = KeyEvent.VK_META;
948                                        break;
949                                case 63289:
950                                case 144:
951                                        keyboardCode = KeyEvent.VK_NUM_LOCK;
952                                        break;
953                                case 63249:
954                                case 145:
955                                        keyboardCode = KeyEvent.VK_SCROLL_LOCK;
956                                        break;
957
958                                /** Constant for the F1 function key. */
959                                case 63236:
960                                case 112:
961                                        keyboardCode = KeyEvent.VK_F1;
962                                        break;
963
964                                /** Constant for the F2 function key. */
965                                case 63237:
966                                case 113:
967                                        keyboardCode = KeyEvent.VK_F2;
968                                        break;
969
970                                /** Constant for the F3 function key. */
971                                case 63238:
972                                case 114:
973                                        keyboardCode = KeyEvent.VK_F3;
974                                        break;
975
976                                /** Constant for the F4 function key. */
977                                case 63239:
978                                case 115:
979                                        keyboardCode = KeyEvent.VK_F4;
980                                        break;
981
982                                /** Constant for the F5 function key. */
983                                case 63240:
984                                case 116:
985                                        keyboardCode = KeyEvent.VK_F5;
986                                        break;
987
988                                /** Constant for the F6 function key. */
989                                case 63241:
990                                case 117:
991                                        keyboardCode = KeyEvent.VK_F6;
992                                        break;
993
994                                /** Constant for the F7 function key. */
995                                case 63242:
996                                case 118:
997                                        keyboardCode = KeyEvent.VK_F7;
998                                        break;
999
1000                                /** Constant for the F8 function key. */
1001                                case 63243:
1002                                case 119:
1003                                        keyboardCode = KeyEvent.VK_F8;
1004                                        break;
1005
1006                                /** Constant for the F9 function key. */
1007                                case 63244:
1008                                case 120:
1009                                        keyboardCode = KeyEvent.VK_F9;
1010                                        break;
1011
1012                                /** Constant for the F10 function key. */
1013                                case 63245:
1014                                case 121:
1015                                        keyboardCode = KeyEvent.VK_F10;
1016                                        break;
1017
1018                                /** Constant for the F11 function key. */
1019                                case 63246:
1020                                case 122:
1021                                        keyboardCode = KeyEvent.VK_F11;
1022                                        break;
1023
1024                                /** Constant for the F12 function key. */
1025                                case 63247:
1026                                case 123:
1027                                        keyboardCode = KeyEvent.VK_F12;
1028                                        break;
1029
1030                                /**
1031                                 * Constant for the F13 function key.
1032                                 *
1033                                 * @since 1.2
1034                                 */
1035                                /*
1036                                 * F13 - F24 are used on IBM 3270 keyboard; break; use
1037                                 * random range for constants.
1038                                 */
1039                                case 124:
1040                                        keyboardCode = KeyEvent.VK_F13;
1041                                        break;
1042
1043                                /**
1044                                 * Constant for the F14 function key.
1045                                 *
1046                                 * @since 1.2
1047                                 */
1048                                case 125:
1049                                        keyboardCode = KeyEvent.VK_F14;
1050                                        break;
1051
1052                                /**
1053                                 * Constant for the F15 function key.
1054                                 *
1055                                 * @since 1.2
1056                                 */
1057                                case 126:
1058                                        keyboardCode = KeyEvent.VK_F15;
1059                                        break;
1060
1061                                case 63302:
1062                                case 45:
1063                                        keyboardCode = KeyEvent.VK_INSERT;
1064                                        break;
1065                                case 47:
1066                                        keyboardCode = KeyEvent.VK_HELP;
1067                                        break;
1068                                default:
1069                                        keyboardCode = keyCode;
1070
1071                        }
1072                }
1073                log("Attempting to type " + (char) charCode + ":"
1074                                + charCode + " " + keyCode);
1075                log("Converted to " + keyboardCode);
1076                return keyboardCode;
1077        }
1078
1079        private boolean isUnsafe(int keyboardCode){
1080                // run through exemption list
1081                log("ctrl: "+ctrl+", alt: "+alt+", shift: "+shift);
1082                if(((ctrl || alt) && keyboardCode == KeyEvent.VK_ESCAPE)
1083                                                        || (alt && keyboardCode == KeyEvent.VK_TAB)
1084                                                        || (ctrl && alt && keyboardCode == KeyEvent.VK_DELETE)){
1085                        log("You are not allowed to press this key combination!");
1086                        return true;
1087                // bugged keys cases go next
1088                }else{
1089                        log("Safe to press.");
1090                        return false;
1091                }
1092        }
1093
1094        private boolean disableNumlock(int vk, boolean shift){
1095                boolean result = !numlockDisabled&&shift
1096                        &&os.indexOf("WINDOWS")!=-1
1097                        &&toolkit.getLockingKeyState(KeyEvent.VK_NUM_LOCK) // only works on Windows
1098                        &&(
1099                                // any numpad buttons are suspect
1100                                vk==KeyEvent.VK_LEFT
1101                                ||vk==KeyEvent.VK_UP
1102                                ||vk==KeyEvent.VK_RIGHT
1103                                ||vk==KeyEvent.VK_DOWN
1104                                ||vk==KeyEvent.VK_HOME
1105                                ||vk==KeyEvent.VK_END
1106                                ||vk==KeyEvent.VK_PAGE_UP
1107                                ||vk==KeyEvent.VK_PAGE_DOWN
1108                );
1109                log("disable numlock: "+result);
1110                return result;
1111        }
1112
1113        private void _typeKey(final int cCode, final int kCode, final boolean a,
1114                        final boolean c, final boolean s, final boolean m){
1115                AccessController.doPrivileged(new PrivilegedAction(){
1116                        public Object run(){
1117                                int charCode = cCode;
1118                                int keyCode = kCode;
1119                                boolean alt = a;
1120                                boolean ctrl = c;
1121                                boolean shift = s;
1122                                boolean meta = m;
1123                                boolean altgraph = false;
1124                                log("> _typeKey Robot " + charCode + ", " + keyCode);
1125                                try{
1126                                        int keyboardCode=getVKCode(charCode, keyCode);
1127                                        if(charCode >= 32){
1128                                                // if it is printable, then it lives in our hashmap
1129                                                KeyEvent event = (KeyEvent) charMap.get(new Integer(charCode));
1130                                                // see if we need to press shift to generate this
1131                                                // character
1132                                                if(!shift){
1133                                                        shift = event.isShiftDown();
1134                                                }
1135                                                altgraph = event.isAltGraphDown();
1136                                                keyboardCode = event.getKeyCode();
1137                                        }
1138
1139                                        // Java bug: on Windows, shift+arrow key unpresses shift when numlock is on.
1140                                        // See: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4838497
1141                                        boolean disableNumlock=disableNumlock(keyboardCode,shift||applet().shift);
1142                                        // run through exemption list
1143                                        if(!isUnsafe(keyboardCode)){
1144                                                if(shift){
1145                                                        log("Pressing shift");
1146                                                        robot.keyPress(KeyEvent.VK_SHIFT);
1147                                                }
1148                                                if(alt){
1149                                                        log("Pressing alt");
1150                                                        robot.keyPress(KeyEvent.VK_ALT);
1151                                                }
1152                                                if(altgraph){
1153                                                        log("Pressing altgraph");
1154                                                        robot.keyPress(KeyEvent.VK_ALT_GRAPH);
1155                                                }
1156                                                if(ctrl){
1157                                                        log("Pressing ctrl");
1158                                                        robot.keyPress(KeyEvent.VK_CONTROL);
1159                                                }
1160                                                if(meta){
1161                                                        log("Pressing meta");
1162                                                        robot.keyPress(KeyEvent.VK_META);
1163                                                }
1164                                                if(disableNumlock){
1165                                                        robot.keyPress(KeyEvent.VK_NUM_LOCK);
1166                                                        robot.keyRelease(KeyEvent.VK_NUM_LOCK);
1167                                                        numlockDisabled=true;
1168                                                }else if(numlockDisabled&&!(applet().shift||shift)){
1169                                                        // only turn it back on when the user is finished pressing shifted arrow keys
1170                                                        robot.keyPress(KeyEvent.VK_NUM_LOCK);
1171                                                        robot.keyRelease(KeyEvent.VK_NUM_LOCK);
1172                                                        numlockDisabled=false;
1173                                                }
1174                                                if(keyboardCode != KeyEvent.VK_SHIFT
1175                                                                && keyboardCode != KeyEvent.VK_ALT
1176                                                                && keyboardCode != KeyEvent.VK_ALT_GRAPH
1177                                                                && keyboardCode != KeyEvent.VK_CONTROL
1178                                                                && keyboardCode != KeyEvent.VK_META){
1179                                                        try{
1180                                                                robot.keyPress(keyboardCode);
1181                                                                robot.keyRelease(keyboardCode);
1182                                                        }catch(Exception e){
1183                                                                log("Error while actually typing a key");
1184                                                                e.printStackTrace();
1185                                                        }
1186
1187                                                }
1188                                                if(ctrl){
1189                                                        robot.keyRelease(KeyEvent.VK_CONTROL);
1190                                                        ctrl = false;
1191                                                }
1192                                                if(alt){
1193                                                        robot.keyRelease(KeyEvent.VK_ALT);
1194                                                        alt = false;
1195                                                }
1196                                                if(altgraph){
1197                                                        robot.keyRelease(KeyEvent.VK_ALT_GRAPH);
1198                                                        altgraph = false;
1199                                                }
1200                                                if(shift){
1201                                                        log("Releasing shift");
1202                                                        robot.keyRelease(KeyEvent.VK_SHIFT);
1203                                                        shift = false;
1204                                                }
1205                                                if(meta){
1206                                                        log("Releasing meta");
1207                                                        robot.keyRelease(KeyEvent.VK_META);
1208                                                        meta = false;
1209                                                }
1210                                        }
1211                                }catch(Exception e){
1212                                        log("Error in _typeKey");
1213                                        e.printStackTrace();
1214                                }
1215                                log("< _typeKey Robot");
1216                                return null;
1217                        }
1218                });
1219        }
1220
1221        public boolean hasFocus(){
1222                // sanity check to make sure the robot isn't clicking outside the window when the browser is minimized for instance
1223                try{
1224                        boolean result= ((Boolean) window
1225                                        .eval("var result=false;if(window.parent.document.hasFocus){result=window.parent.document.hasFocus();}else{result=true;}result;"))
1226                                        .booleanValue();
1227                        if(!result){
1228                                // can happen for instance if the browser minimized itself, or if there is another applet on the page.
1229                                // recompute window,mouse positions to see if it is still safe to continue.
1230                                log("Document focus lost. Recomputing window position");
1231                                Point p = getLocationOnScreen();
1232                                log("Old root: "+docScreenX+" "+docScreenY);
1233                                docScreenX=p.x-margin.x;
1234                                docScreenY=p.y-margin.y;
1235                                log("New root: "+docScreenX+" "+docScreenY);
1236                                docScreenXMax=docScreenX+((Integer)window.eval("window.parent.document.getElementById('dohrobotview').offsetLeft")).intValue();
1237                                docScreenYMax=docScreenY+((Integer)window.eval("window.parent.document.getElementById('dohrobotview').offsetTop")).intValue();
1238                                // bring browser to the front again.
1239                                // if the window just blurred and moved, key events will again be directed to the window.
1240                                // 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.
1241                                window.eval("window.parent.focus();");
1242                                // recompute mouse position
1243                                return isSecure(this.key);
1244                        }else{
1245                                return result;
1246                        }
1247                }catch(Exception e){
1248                        // runs even after you close the window!
1249                        return false;
1250                }
1251        }
1252
1253        // Threads for common Robot tasks
1254        // (so as not to tie up the browser rendering thread!)
1255        // declared inside so they have private access to the robot
1256        // we do *not* want to expose that guy!
1257        private class ProfilingThread implements Runnable{
1258                protected long delay=0;
1259                protected long duration=0;
1260                private long start;
1261                private long oldDelay;
1262                protected void startProfiling(){
1263                        // error correct
1264                        if(delay>0){
1265                                oldDelay=delay;
1266                                delay-=timingError+(duration>0?timingError:0);
1267                                log("Timing error: "+timingError);
1268                                if(delay<1){
1269                                        if(duration>0){ duration=Math.max(duration+delay,1); }
1270                                        delay=1;
1271                                }
1272                                start=System.currentTimeMillis();
1273                        }else{
1274                                // assumption is that only doh.robot.typeKeys actually uses delay/needs this level of error correcting
1275                                timingError=0;
1276                        }
1277                }
1278                protected void endProfiling(){
1279                        // adaptively correct timingError
1280                        if(delay>0){
1281                                long end=System.currentTimeMillis();
1282                                timingError+=(end-start)-oldDelay;
1283                        }
1284                }
1285                public void run(){}
1286        }
1287       
1288        final private class KeyPressThread extends ProfilingThread{
1289                private int charCode;
1290                private int keyCode;
1291                private boolean alt;
1292                private boolean ctrl;
1293                private boolean shift;
1294                private boolean meta;
1295
1296                public KeyPressThread(int charCode, int keyCode, boolean alt,
1297                                boolean ctrl, boolean shift, boolean meta, int delay){
1298                        log("KeyPressThread constructor " + charCode + ", " + keyCode);
1299                        this.charCode = charCode;
1300                        this.keyCode = keyCode;
1301                        this.alt = alt;
1302                        this.ctrl = ctrl;
1303                        this.shift = shift;
1304                        this.meta = meta;
1305                        this.delay = delay;
1306                }
1307
1308                public void run(){
1309                        try{
1310                                startProfiling();
1311                                // in different order so async works
1312                                while(!hasFocus()){
1313                                        Thread.sleep(1000);
1314                                }
1315                                Thread.sleep(delay);
1316                                log("> run KeyPressThread");
1317
1318                                _typeKey(charCode, keyCode, alt, ctrl, shift, meta);
1319
1320                                endProfiling();
1321                        }catch(Exception e){
1322                                log("Bad parameters passed to _typeKey");
1323                                e.printStackTrace();
1324                        }
1325                        log("< run KeyPressThread");
1326
1327                }
1328        }
1329
1330        final private class KeyDownThread extends ProfilingThread{
1331                private int charCode;
1332                private int keyCode;
1333
1334                public KeyDownThread(int charCode, int keyCode, int delay){
1335                        log("KeyDownThread constructor " + charCode + ", " + keyCode);
1336                        this.charCode = charCode;
1337                        this.keyCode = keyCode;
1338                        this.delay = delay;
1339                }
1340
1341                public void run(){
1342                        try{
1343                                Thread.sleep(delay);
1344                                log("> run KeyDownThread");
1345                                while(!hasFocus()){
1346                                        Thread.sleep(1000);
1347                                }
1348                                int vkCode=getVKCode(charCode, keyCode);
1349                                if(charCode >= 32){
1350                                        // if it is printable, then it lives in our hashmap
1351                                        KeyEvent event = (KeyEvent) charMap.get(new Integer(charCode));
1352                                        // see if we need to press shift to generate this
1353                                        // character
1354                                        if(event.isShiftDown()){
1355                                                robot.keyPress(KeyEvent.VK_SHIFT);
1356                                                shift=true;
1357                                        }
1358                                        if(event.isAltGraphDown()){
1359                                                robot.keyPress(KeyEvent.VK_ALT_GRAPH);
1360                                                altgraph=true;
1361                                        }
1362                                }else{
1363                                        if(vkCode==KeyEvent.VK_ALT){
1364                                                alt=true;
1365                                        }else if(vkCode==KeyEvent.VK_CONTROL){
1366                                                ctrl=true;
1367                                        }else if(vkCode==KeyEvent.VK_SHIFT){
1368                                                shift=true;
1369                                        }else if(vkCode==KeyEvent.VK_ALT_GRAPH){
1370                                                altgraph=true;
1371                                        }else if(vkCode==KeyEvent.VK_META){
1372                                                meta=true;
1373                                        }else if(disableNumlock(vkCode,shift)){
1374                                                robot.keyPress(KeyEvent.VK_NUM_LOCK);
1375                                                robot.keyRelease(KeyEvent.VK_NUM_LOCK);
1376                                                numlockDisabled=true;
1377                                        }
1378                                }
1379                                if(!isUnsafe(vkCode)){
1380                                        robot.keyPress(vkCode);
1381                                }
1382                        }catch(Exception e){
1383                                log("Bad parameters passed to downKey");
1384                                e.printStackTrace();
1385                        }
1386                        log("< run KeyDownThread");
1387
1388                }
1389        }
1390
1391        final private class KeyUpThread extends ProfilingThread{
1392                private int charCode;
1393                private int keyCode;
1394
1395                public KeyUpThread(int charCode, int keyCode, int delay){
1396                        log("KeyUpThread constructor " + charCode + ", " + keyCode);
1397                        this.charCode = charCode;
1398                        this.keyCode = keyCode;
1399                        this.delay = delay;
1400                }
1401
1402                public void run(){
1403                        try{
1404                                Thread.sleep(delay);
1405                                log("> run KeyUpThread");
1406                                while(!hasFocus()){
1407                                        Thread.sleep(1000);
1408                                }
1409                                int vkCode=getVKCode(charCode, keyCode);
1410                                if(charCode >= 32){
1411                                        // if it is printable, then it lives in our hashmap
1412                                        KeyEvent event = (KeyEvent) charMap.get(new Integer(charCode));
1413                                        // see if we need to press shift to generate this
1414                                        // character
1415                                        if(event.isShiftDown()){
1416                                                robot.keyRelease(KeyEvent.VK_SHIFT);
1417                                                shift=false;
1418                                        }
1419                                        if(event.isAltGraphDown()){
1420                                                robot.keyRelease(KeyEvent.VK_ALT_GRAPH);
1421                                                altgraph=false;
1422                                        }
1423                                }else{
1424                                        if(vkCode==KeyEvent.VK_ALT){
1425                                                alt=false;
1426                                        }else if(vkCode==KeyEvent.VK_CONTROL){
1427                                                ctrl=false;
1428                                        }else if(vkCode==KeyEvent.VK_SHIFT){
1429                                                shift=false;
1430                                                if(numlockDisabled){
1431                                                        robot.keyPress(KeyEvent.VK_NUM_LOCK);
1432                                                        robot.keyRelease(KeyEvent.VK_NUM_LOCK);
1433                                                        numlockDisabled=false;
1434                                                }
1435                                        }else if(vkCode==KeyEvent.VK_ALT_GRAPH){
1436                                                altgraph=false;
1437                                        }else if(vkCode==KeyEvent.VK_META){
1438                                                meta=false;
1439                                        }
1440                                }
1441                                robot.keyRelease(vkCode);
1442                        }catch(Exception e){
1443                                log("Bad parameters passed to upKey");
1444                                e.printStackTrace();
1445                        }
1446                        log("< run KeyUpThread");
1447
1448                }
1449        }
1450
1451        final private class MousePressThread extends ProfilingThread{
1452                private int mask;
1453
1454                public MousePressThread(int mask, int delay){
1455                        this.mask = mask;
1456                        this.delay = delay;
1457                }
1458
1459                public void run(){
1460                        try{
1461                                Thread.sleep(delay);
1462                                log("> run MousePressThread");
1463                                while(!hasFocus()){
1464                                        Thread.sleep(1000);
1465                                }
1466                                robot.mousePress(mask);
1467                                robot.waitForIdle();
1468                        }catch(Exception e){
1469                                log("Bad parameters passed to mousePress");
1470                                e.printStackTrace();
1471                        }
1472                        log("< run MousePressThread");
1473
1474                }
1475        }
1476
1477        final private class MouseReleaseThread extends ProfilingThread{
1478                private int mask;
1479
1480                public MouseReleaseThread(int mask, int delay){
1481                        this.mask = mask;
1482                        this.delay = delay;
1483                }
1484
1485                public void run(){
1486                        try{
1487                                Thread.sleep(delay);
1488                                log("> run MouseReleaseThread ");
1489                                while(!hasFocus()){
1490                                        Thread.sleep(1000);
1491                                }
1492                                robot.mouseRelease(mask);
1493                                robot.waitForIdle();
1494                        }catch(Exception e){
1495                                log("Bad parameters passed to mouseRelease");
1496                                e.printStackTrace();
1497                        }
1498
1499                        log("< run MouseReleaseThread ");
1500
1501                }
1502        }
1503
1504        final private class MouseMoveThread extends ProfilingThread{
1505                private int x;
1506                private int y;
1507
1508                public MouseMoveThread(int x, int y, int delay, int duration){
1509                        this.x = x;
1510                        this.y = y;
1511                        this.delay = delay;
1512                        this.duration = duration;
1513                }
1514
1515                public double easeInOutQuad(double t, double b, double c, double d){
1516                        t /= d / 2;
1517                        if(t < 1)
1518                                return c / 2 * t * t + b;
1519                        t--;
1520                        return -c / 2 * (t * (t - 2) - 1) + b;
1521                };
1522
1523                public void run(){
1524                        try{
1525                                Thread.sleep(delay);
1526                                log("> run MouseMoveThread " + x + ", " + y);
1527                                while(!hasFocus()){
1528                                        Thread.sleep(1000);
1529                                }
1530                                int x1 = lastMouseX;
1531                                int x2 = x;
1532                                int y1 = lastMouseY;
1533                                int y2 = y;
1534                                // shrink range by 1 px on both ends
1535                                // manually move this 1px to trip DND code
1536                                if(x1 != x2){
1537                                        int dx = x - lastMouseX;
1538                                        if(dx > 0){
1539                                                x1 += 1;
1540                                                x2 -= 1;
1541                                        }else{
1542                                                x1 -= 1;
1543                                                x2 += 1;
1544                                        }
1545                                }
1546                                if(y1 != y2){
1547                                        int dy = y - lastMouseY;
1548                                        if(dy > 0){
1549                                                y1 += 1;
1550                                                y2 -= 1;
1551                                        }else{
1552                                                y1 -= 1;
1553                                                y2 += 1;
1554                                        }
1555
1556                                }
1557                                // manual precision
1558                                robot.setAutoWaitForIdle(false);
1559                                int intermediateSteps = duration==1?0: // duration==1 -> user wants to jump the mouse
1560                                        ((((int)Math.ceil(Math.log(duration+1)))|1)); // |1 to ensure an odd # of intermediate steps for sensible interpolation
1561                                // assumption: intermediateSteps will always be >=0
1562                                int delay = (int)duration/(intermediateSteps+1); // +1 to include last move
1563                                // First mouse movement fires at t=0 to official last know position of the mouse.
1564                                robot.mouseMove(lastMouseX, lastMouseY);
1565                                long start,end;
1566                               
1567                                // Shift lastMouseX/Y in the direction of the movement for interpolating over the smaller interval.
1568                                lastMouseX=x1;
1569                                lastMouseY=y1;
1570                                // Now interpolate mouse movement from (lastMouseX=x1,lastMouseY=y1) to (x2,y2)
1571                                // precondition: the amount of time that has passed since the first mousemove is 0*delay.
1572                                // invariant: each time you end an iteration, after you increment t, the amount of time that has passed is t*delay
1573                                int timingError=0;
1574                                for (int t = 0; t < intermediateSteps; t++){
1575                                        start=new Date().getTime();
1576                                        Thread.sleep(delay);
1577                                        x1 = (int) easeInOutQuad((double) t, (double) lastMouseX,
1578                                                        (double) x2 - lastMouseX, (double) intermediateSteps-1);
1579                                        y1 = (int) easeInOutQuad((double) t, (double) lastMouseY,
1580                                                        (double) y2 - lastMouseY, (double) intermediateSteps-1);
1581                                        //log("("+x1+","+y1+")");
1582                                        robot.mouseMove(x1, y1);
1583                                        end=new Date().getTime();
1584                                        // distribute error among remaining steps
1585                                        timingError=(((int)(end-start))-delay)/(intermediateSteps-t);
1586                                        log("mouseMove timing error: "+timingError);
1587                                        delay=Math.max(delay-(int)timingError,1);
1588                                }
1589                                // postconditions:
1590                                //      t=intermediateSteps
1591                                //      intermediateSteps*delay time has passed,
1592                                //      time remaining = duration-intermediateSteps*delay = (steps+1)*delay-intermediateSteps*delay = delay
1593                                // You theoretically need 1 more delay for the whole duration to have passed.
1594                                // In practice, you want less than that due to roundoff errors in Java's clock granularity.
1595                                Thread.sleep(delay);
1596                                robot.mouseMove(x, y);
1597                                robot.setAutoWaitForIdle(true);
1598                               
1599                                //log("mouseMove statistics: duration= "+duration+" steps="+intermediateSteps+" delay="+delay);
1600                                //log("mouseMove discrepency: "+(date2-date-duration)+"ms");
1601                                lastMouseX = x;
1602                                lastMouseY = y;
1603                        }catch(Exception e){
1604                                log("Bad parameters passed to mouseMove");
1605                                e.printStackTrace();
1606                        }
1607
1608                        log("< run MouseMoveThread");
1609
1610                }
1611        }
1612
1613        final private class MouseWheelThread extends ProfilingThread{
1614                private int amount;
1615
1616                public MouseWheelThread(int amount, int delay, int duration){
1617                        this.amount = amount;
1618                        this.delay = delay;
1619                        this.duration = duration;
1620                }
1621
1622                public void run(){
1623                        try{
1624                                Thread.sleep(delay);
1625                                log("> run MouseWheelThread " + amount);
1626                                while(!hasFocus()){
1627                                        Thread.sleep(1000);
1628                                }
1629                                robot.setAutoDelay(Math.max((int)duration/Math.abs(amount),1));
1630                                for(int i=0; i<Math.abs(amount); i++){
1631                                        robot.mouseWheel(amount>0?dir:-dir);
1632                                }
1633                                robot.setAutoDelay(1);
1634                        }catch(Exception e){
1635                                log("Bad parameters passed to mouseWheel");
1636                                e.printStackTrace();
1637                        }
1638                        log("< run MouseWheelThread ");
1639                }
1640        }
1641
1642        final private class RobotSecurityManager extends SecurityManager{
1643                // The applet's original security manager.
1644                // There is a bug in some people's Safaris that causes Safari to
1645                // basically hang on liveconnect calls.
1646                // Our security manager fixes it.
1647
1648                private boolean isActive = false;
1649                private boolean needsSecurityManager = false;
1650                private SecurityManager oldsecurity = null;
1651
1652                public RobotSecurityManager(boolean needsSecurityManager, SecurityManager oldsecurity){
1653                        this.needsSecurityManager = needsSecurityManager;
1654                        this.oldsecurity = oldsecurity;
1655                }
1656
1657                public boolean checkTopLevelWindow(Object window){
1658                        // If our users temporarily accept our cert for a session,
1659                        // then use the same session to browse to a malicious website also using our applet,
1660                        // that website can automatically execute the applet.
1661                        // To resolve this issue, RobotSecurityManager overrides checkTopLevelWindow
1662                        // to check the JVM to see if there are other instances of the applet running on different domains.
1663                        // If there are, it prompts the user to confirm that they want to run the applet before continuing.
1664
1665                        // null is not supposed to be allowed
1666                        // so we allow it to distinguish our security manager.
1667                        if(window == null){
1668                                isActive = !isActive;
1669                                log("Active is now " + isActive);
1670                        }
1671                        return window == null ? true : oldsecurity
1672                                        .checkTopLevelWindow(window);
1673                }
1674
1675                public void checkPermission(Permission p){
1676                        // liveconnect SocketPermission resolve takes
1677                        // FOREVER (like 6 seconds) in Safari 3
1678                        // Java does like 50 of these on the first JS call
1679                        // 6*50=300 seconds!
1680                        if(isActive && needsSecurityManager
1681                                        && java.net.SocketPermission.class.isInstance(p)
1682                                        && p.getActions().matches(".*resolve.*")){
1683                                throw new SecurityException(
1684                                                "DOH: liveconnect resolve locks up Safari 3. Denying resolve request.");
1685                        }else if(p.equals(new java.awt.AWTPermission("watchMousePointer"))){
1686                                // enable robot to watch mouse
1687                        }else{
1688                                oldsecurity.checkPermission(p);
1689                        }
1690                }
1691
1692                public void checkPermission(Permission perm, Object context){
1693                        checkPermission(perm);
1694                }
1695        }
1696       
1697        public void setClipboardText(double sec, final String data) {
1698                if(!isSecure(sec))
1699                        return;
1700                // called by doh.robot.setClipboard
1701                // see it for details
1702                AccessController.doPrivileged(new PrivilegedAction(){
1703                        public Object run(){
1704                                StringSelection ss = new StringSelection(data);
1705                                getSystemClipboard().setContents(ss, ss);
1706                                return null;
1707                        }
1708                });
1709        }
1710       
1711        public void setClipboardHtml(double sec, final String data) {
1712                if(!isSecure(sec))
1713                        return;
1714                // called by doh.robot.setClipboard when format=='text/html'
1715                // see it for details
1716                AccessController.doPrivileged(new PrivilegedAction(){
1717                        public Object run(){
1718                            String mimeType = "text/html;class=java.lang.String";//type + "; charset=" + charset;// + "; class=" + transferType;
1719                            TextTransferable transferable = new TextTransferable(mimeType, data);
1720                            getSystemClipboard().setContents(transferable, transferable);
1721                                return null;
1722                        }
1723                });
1724        }
1725        private static java.awt.datatransfer.Clipboard getSystemClipboard() {
1726                return toolkit.getSystemClipboard();
1727        }
1728       
1729        private static class TextTransferable implements Transferable, ClipboardOwner {
1730                private String data;
1731                private static ArrayList htmlFlavors = new ArrayList();
1732               
1733                static{
1734                        try{
1735                                htmlFlavors.add(new DataFlavor("text/plain;charset=UTF-8;class=java.lang.String"));
1736                                htmlFlavors.add(new DataFlavor("text/html;charset=UTF-8;class=java.lang.String"));
1737                        }catch(ClassNotFoundException ex){
1738                                ex.printStackTrace();
1739                        }
1740                }
1741               
1742               
1743                public TextTransferable(String mimeType, String data){
1744                        this.data = data;
1745                }
1746               
1747                public DataFlavor[] getTransferDataFlavors(){
1748                        return (DataFlavor[]) htmlFlavors.toArray(new DataFlavor[htmlFlavors.size()]);
1749                }
1750               
1751                public boolean isDataFlavorSupported(DataFlavor flavor){
1752                        return htmlFlavors.contains(flavor);
1753                }
1754               
1755                public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException{
1756                        if (String.class.equals(flavor.getRepresentationClass())){
1757                        return data;
1758                    }
1759               
1760                    throw new UnsupportedFlavorException(flavor);
1761               
1762                }
1763               
1764                public void lostOwnership(java.awt.datatransfer.Clipboard clipboard, Transferable contents){
1765                        data = null;
1766                }
1767        }
1768}
Note: See TracBrowser for help on using the repository browser.