advertisement

Print

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

Pitfall #6: scrollIntoView

In the context of a regular web page, you can manipulate the viewport to scroll the page programmatically. For example, this code will find the page element named foo and scroll the browser window to make the element visible on screen:

var elmFoo = document.getElementById('foo');
elmFoo.scrollIntoView();

This does not work in Greasemonkey scripts, because elmFoo is an XPCNativeWrapper, and XPCNativeWrappers do not call the scrollIntoView method on the underlying wrapped element. Instead, you need to use the special wrappedJSObject property of the XPCNativeWrapper object to get a reference to the real element, and then call its scrollIntoView method:

var elmFoo = document.getElementById('foo');
var elmUnderlyingFoo = elmFoo.wrappedJSObject || elmFoo;
elmUnderlyingFoo.scrollIntoView();

It is important to note that this is vulnerable to a malicious remote page redefining the scrollIntoView method to do something other than scrolling the viewport. There is no general solution to this problem.

Pitfall #7: Location

There are several ways for regular JavaScript code to work with the current page URL. The window.location object contains information about the current URL, including href (the full URL), hostname (the domain name), and pathname (the part of the URL after the domain name). You can programmatically move to a new page by setting window.location.href to another URL. But there is also shorthand for this. The window.location object defines its href attribute as a default property, which means that you can move to a new page simply by setting window.location:

window.location = "http://example.com/";

In regular JavaScript code, this sets the window.location.href property, which jumps to the new page. But in Greasemonkey scripts, this doesn't work, because the window object is an XPCNativeWrapper, and XPCNativeWrappers don't support setting the default properties of the wrapped object. This means that setting window.location in a Greasemonkey script will not actually jump to a new page. Instead, you need to explicitly set window.location.href:

window.location.href = "http://example.com/";

This also applies to the document.location object.

Pitfall #8: Calling Remote Page Scripts

Occasionally, a user script needs to call a function defined by the remote page. For example, there are several Greasemonkey scripts that integrate with Gmail, Google's web mail service. Gmail is heavily dependent on JavaScript, and user scripts that wish to extend it frequently need to call functions that the original page has defined:

var searchForm = getNode("s");
searchForm.elements.namedItem("q").value = this.getRunnableQuery();
top.js._MH_OnSearch(window, 0);

The original page scripts don't expect to get XPCNativeWrappers as parameters. Here, the _MH_OnSearch function defined by the original page expects the real window as its first argument, not an XPCNativeWrapper around the window. To solve this problem, Greasemonkey defines a special variable, unsafeWindow, which is a reference to the actual window object.

var searchForm = getNode("s");
searchForm.elements.namedItem("q").value = this.getRunnableQuery();
top.js._MH_OnSearch(unsafeWindow, 0);

It's called unsafeWindow for a reason: its properties and methods could be redefined by the page to do virtually anything. You should never call methods on unsafeWindow unless you completely trust the remote page not to mess with you. You should only ever use it as a parameter to call functions defined by the original page, or to watch window properties, as shown in the next section.

Greasemonkey also defines unsafeDocument, which is the actual document object. As with unsafeWindow, you should never use it except to pass it as a parameter to page scripts that expect the actual document object.

Pitfall #9: watch

Earlier in this hack, I mentioned the watch method, which is available on every JavaScript object. It allows you to intercept assignments to an object's properties. For instance, you could set up a watch on the window.location object to watch for scripts that tried to navigate to a new page programmatically:

window.watch("location", watchLocation);
window.location.watch("href", watchLocation);

In the context of a user script, this will not work. You need to set the watch on the unsafeWindow object:

unsafeWindow.watch("location", watchLocation);
unsafeWindow.location.watch("href", watchLocation);

Note that this is still vulnerable to a malicious page redefining the watch method itself. There is no general solution to this problem.

Pitfall #10: style

In JavaScript, every element has a style attribute, with which you can get and set the element's CSS styles. Firefox also supports a shorthand method for setting multiple styles at once:

var elmFoo = document.getElementById("foo");
elmFoo.setAttribute("style", "margin:0; padding:0;");

This does not work in Greasemonkey scripts, because the object returned by document.getElementById is an XPCNativeWrapper, and XPCNativeWrappers do not support this shorthand for setting CSS styles in bulk. You will need to set each style individually:

var elmFoo = document.getElementById("foo");
elmFoo.style.margin = 0;
elmFoo.style.padding = 0;

Conclusion

This is a long and complicated hack, and if you're not thoroughly confused by now, you probably haven't been paying attention. The security concerns that prompted the architectural changes in Greasemonkey 0.5 are both subtle and complex, but it's important that you understand them.

The tradeoff for this increased security is increased complexity, specifically the limitations and quirks of XPCNativeWrappers. There is not much I can do to make this easier to digest, except to assure you that all of the scripts in Greasemonkey Hacks work. I have personally updated all of them and tested them extensively in Greasemonkey 0.5. They can serve as blueprints for your own hacks.

Mark Pilgrim is an accessibility architect who can be found stirring up trouble at diveintomark.org.


Return to the O'Reilly Network