source: Dev/trunk/src/client/util/doh/robot/DOHRobot.java @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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