advertisement

Print

Avoid Common Pitfalls in Greasemonkey
Pages: 1, 2, 3, 4, 5

Pitfall #1: Auto-eval Strings

In places where you want to set up a callback function (such as window.setTimeout, to run a function after a delay), JavaScript allows you to define the callback as a string. When it's time to execute the callback, Firefox evaluates the string and executes it. This leads to our first pitfall.



Assuming a user script defines a function called my_func, this code looks like it will execute my_func() after a one-second delay:

window.setTimeout("my_func()", 1000);

This doesn't work in a Greasemonkey script; the my_func function will never execute. By the time the callback executes one second later, the user script and its entire sandbox have disappeared. The window.setTimeout function will try to evaluate the JavaScript code in the context of the page as it exists one second later, but the page doesn't include the my_func function. In fact, it never included the my_func function; that function only ever existed within the Greasemonkey sandbox.

This doesn't mean you can never use timeouts, though. You just need to set them up differently. Here is the same code, but written in a way that works in the context of a user script:

window.setTimeout(my_func, 1000);

What's the difference? The my_func function is referenced directly, as an object instead of a string. You are passing a function reference to the window.setTimeout function, which will store the reference until it is time to execute it. When the time comes, it can still call the my_func function, because JavaScript keeps the function's environment alive as long as something, somewhere, is holding a reference to it.

Pitfall #2: Event Handlers

Another common pattern in JavaScript is setting event handlers, such as onclick, onchange, or onsubmit. The most common way to set up an onclick event handler is to assign a string to an element's onclick property:

var elmLink = document.getElementById('somelink');
elmLink.onclick = 'my_func(this)';

This technique fails in a user script for the same reason the first window.setTimeout call failed. By the time the user clicks the link, the my_func function defined elsewhere in the user script will no longer exist.

OK, let's try setting the onclick callback directly:

var elmLink = document.getElementById('somelink');
elmLink.onclick = my_func;

This also fails, but for a completely different reason. The document.getElementById function returns an XPCNativeWrapper around an Element object, not the element itself. That means that setting elmLink.onclick to a function reference sets a property not on the element, but on the XPCNativeWrapper. With most properties, such as id or className, the XPCNativeWrapper will turn around and set the corresponding property on the underlying element. But due to limitations of how XPCNativeWrappers are implemented, this pass-through does not work with event handlers such as onclick. This example code will not set the corresponding onclick handler on the actual element, and when you click the link, my_func will not execute.

This doesn't mean you can't set event handlers; just that you can't set them in the obvious way. The only technique that works is the addEventListener method:

var elmLink = document.getElementById('somelink');
elmLink.addEventListener("click", my_func, true);

This technique works with all elements, as well as the window and document objects. It works with all DOM events, including click, change, submit, keypress, mousemove, and so on. It works with existing elements on the page that you find by calling document.getElementsByTagName or document.getElementById, and it works with new elements you create dynamically by calling document.createElement. It is the only way to set event handlers that works in the context in which user scripts operate.

Pages: 1, 2, 3, 4, 5

Next Pagearrow