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
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 11 of 11.
-
Security Hole #1: Source Code Leakage
2005-11-15 07:38:06 random_ [Reply | View]
So the web page can download your user script. Big deal. Why is this a security hole? Please explain.
-
Sensitive Information
2006-01-31 19:42:28 markmascolino [Reply | View]
Because the user script might have usernames and passwords in it for 3rd party sites.
-
Another way for calling Remote Page Scripts
2006-08-14 15:24:19 .::jp::. [Reply | View]
I'm new with greasemonkey and i've been using another way for calling Remote Pages Scripts, making use of "window.location.href", example:
Calling a function:
window.location.href="javascript: RemotePagefunction("+arguments+")";
Changing a var:
window.location.href="javascript: void(localvar='foo')";
With this method you can't receive parameters from the function.
-
Excellent! Thanks...
2006-12-13 04:49:28 PhiLho [Reply | View]
Great article, it solved the mystery why a script I injected in the page (using an appendChild of a script element) didn't worked when moved in a standalone GM script.
I was annoyed to see I couldn't set properties to a created element: I add a button to act on a div, so I wrote: button.targetDiv = d; to keep a reference to the div element to act upon.
I can't use the setAttribute trick, as I believe it is limited to textual attributes.
But I can use button.wrappedJSObject.targetDiv = d; which should be relatively safe (no sensible info here!)
-
Is it work?
2007-03-27 01:38:15 LungZeno [Reply | View]
>> var elmFoo = document.getElementById('foo');
>> var elmUnderlyingFoo = elmFoo.wrappedJSObject || >> elmFoo;
>> elmUnderlyingFoo.scrollIntoView();
var elmFoo = document.getElementById('foo');
var elmUnderlyingFoo = elmFoo.wrappedJSObject || elmFoo;
elmFoo.scrollIntoView.call(elmUnderlyingFoo);
>> unsafeWindow.watch("location", watchLocation);
>> unsafeWindow.location.watch("href", watchLocation);
window.watch.call(unsafeWindow,"location", watchLocation);
window.location.watch.call(unsafeWindow.location,"href", watchLocation);
Is it general solution to this problem?
-
Security of wrappedJSObject
2008-01-02 16:23:49 Lunatic_Lycanthrop [Reply | View]
I don't fully understand why is wrappedJSObject insecure. For example, as a workaround to the "style" attribute problem, you could use the following code:
document.wrappedJSObject.body.setAttribute("style","background-color:#f90; font-size: 14px;");
... and so, yo could set a large amount of attributes in only one string.
However, the page of XPCNative wrapper at mozilla.org advises:
As the name of this section implies, doing so is unsafe. You shouldn't use wrappedJSObject to bypass XPCNativeWrapper in production code.
(http://developer.mozilla.org/en/docs/XPCNativeWrapper)
My question then would be ¿Why? -
Security of wrappedJSObject
2008-02-26 14:42:33 DeBa [Reply | View]
I ask myself the same question. The answer I found is in this article above on Page 1-2. It is unsave, because your Greasemonkey - script could be trapped and stopp running on the site. -
Security of wrappedJSObject
2008-08-24 00:12:32 gaudio [Reply | View]
The primary reason it's unsafe is because it's possible for the remote site to have redefined the 'setAttribute' method on the body. In that case, you are running remote code, which could be doing anything, in the privileged sandbox of Greasemonkey.
If I'm not mistaken, this remote script could be written to grab a hold of some of the privileged objects, especially GM_xmlhttpRequest, and start going crazy with it.
What's really needed is the ability to drop out of the sandbox when calling a method defined from the remote page, whether directly or via an object. Of course, this guide was written about 0.3 Greasemonkey versions ago, so perhaps it's become a bit more secure.




