Controlling Focus in Firefox
By Adrian Sutton
I have a bunch of JavaScript unit tests and for some of them I need to trigger a real keypress – one that activates the browser’s default functionality, not just triggers the JavaScript listeners. To do that, I have a simple signed applet that uses the AWT Robot to programmatically trigger the keypresses. Mostly, this works brilliantly.
The problem I’m having is that about 1 time in 10, the focus goes AWOL and the key events generated by the robot simply aren’t delivered anywhere. It’s generally not reliable, though I have got a setup now where I can make it happen within a few minutes of running the tests on a loop.
The problem is specific to Firefox 3.6 – I’ve never seen it fail on anything else. I’m testing against Firefox 2.0, 3.0, 3.6 and 4.0 beta 6. 3.6.3 and 3.6.12 to be precise. It fails on all the OS’s I’ve tried: Linux (Ubuntu 10.10), Mac OS X (10.6) and Windows Vista.
The Simple Question
Is there a sure-fire way in JavaScript to force focus to a specific element?
The normal Element.focus() doesn’t seem to be strong enough as I’m already calling that (and I’ve tried oh so many variations of it). I’m even ok with requiring that element to be of a specific type (e.g. a text input). JavaScript is ideal but if it can be done with a signed Java applet I’m ok with that too. Config options, browser plugins and maybe even native code would all be a possibility.
The Simple Answer (Update)
It turns out that the ineffectiveness of Element.focus() is a usability/security feature of Firefox which is configurable. In the “Content” tab of preferences, click the “Advanced…” button beside JavaScript and then ensure that “Raise or lower windows” is checked. Annoyingly simple after the amount of effort it took to work out what was going wrong.
The Detail
Here’s what I know:
- The test runner framework loads the actual test pages inside an iframe.
- The signed applet is inside this iframe.
- The test that is giving me trouble uses TinyMCE, which itself uses an iframe, so we have an iframe inside an iframe and that’s where we need to get focus.
- To make sure that the actual browser pane has focus (rather than the URL bar etc), when the page first loads the applet clicks just above itself (thus moving focus to the actual test page inside the first iframe).
- I can watch this happen so I know the click is in the right place.
- The page is definitely fully loaded before the problematic test runs. It’s not the first test in that file but it is the first test that actually uses the robot (the click still happens on page load). I can’t remember a failure where it wasn’t the first test using the robot that failed but can’t be 100% certain of that.
- I’m using the JavaScript focus() method to move focus to the element I’m targeting.
- I’ve found I need to focus the main window, then each nested iframe window and document body and then finally the element I want to target to make sure that the focus is on the right frame.
- I can check that document.activeElement is pointing to the right place.
- When the focus gets lost, even manually typing keys does nothing.
- But typing tab will move the focus back to the right spot and then typing the key the test was expecting will make the test pass correctly.
- The key event is definitely firing from the AWT robot, because if that key is the tab key, it correctly moves the focus back to where it should be (but I have no way to know programmatically that I need to repeat the keystroke).
Why Not Selenium?
Just to cover this off, there are a few reasons that this project isn’t using Selenium:
- It was an existing project which already had tests using QUnit and most of the tests don’t need this Robot functionality so can be done in pure JS.
- It’s a pure JS project so it’s really nice to also write the tests in JavaScript (the robot has a nice JS wrapper).
- This approach lets me run the tests in any browser without requiring any setup at all. No need to install and configure anything – you could very likely walk up to a computer in an internet cafe and have the tests pass which makes life so much easier.
- It should work damn it! I want to know why it doesn’t and why Selenium, which also uses the AWT Robot doesn’t hit the same problem (or maybe it does?).
Murphy’s Law
Just before starting to write this post (some 30 minutes ago due to distractions), I had the crazy idea to add a java.awt.TextField to the applet and make the applet a little bit bigger (150x40px instead of 10x10px). The tests have been running continuously with that new configuration since then without a single failure. The text area is never used in any way, but for some reason just having it there seems to make life better. I don’t get it.
Due to a power outage I can’t run it on the actual build servers until tomorrow so I’m going to post this anyway since you just know it will start failing again just as soon as I think I’ve got it fixed.
Update 10 seconds after posting: Yep, there it goes, failed again.