Working around the same-origin policy in Greasemonkey

The same-origin policy prevents scripts on a page from communicating with servers on a different domain from the page. Implemented by all browsers, this prevents some cross-site scripting attacks.

Of course, communicating with different web services is a common goal for mashups like the ones we’re building in this class. So if you need to communicate with a different server from the current page, you have three options:

  1. Use a server-side proxy.
  2. Make a JSONP request to a server that supports it.
  3. Use the Greasemonkey chrome to make the cross-site request for you.

Ryan’s walkthrough of the Delicious Trailmaker has code for both of the first two options, so I’ll focus on the third. Also, if you are writing a Greasemonkey script, the third option is the most flexible and straightforward.

First, let’s start with a stub of a Greasemonkey script. This script loads jQuery and inserts some simple CSS and HTML into the page to add a bar to the bottom of the page.

Now, let’s use AJAX to request something on another page. You can read extensive documentation on the jQuery AJAX commands, but for the most part, you can just call $.get() with the URL you want and a callback function for handling the XML response. For example, if you run this on http://sfbay.craigslist.org you should see the full contents of the sublets page outputted in your console.

$.get('/sub/', function(xml){
    console.log(xml);
});

But if you modify that to request the contents of delicious.com while still on craigslist.org, you’ll see nothing in the console. (You can try this yourself to convince yourself that the same-origin restriction really works.)

$.get('http://delicious.com/', function(xml){
    console.log(xml);
});

Now, to workaround this restriction, we’ll add a Javascript file which tells jQuery to use Greasemonkey to make its XmlHttpRequests. The Greasemonkey-jQuery-XHR Bridge is available on our course website.

To add it to our Greasemonkey script, we’ll add a new @require statement to the metadata block at the top of the script:

...
// @require	http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js
// @require	http://courses.ischool.berkeley.edu/i290-4/f09/resources/gm_jq_xhr.js
// ==/UserScript==

In order to get these changes, you must uninstall and re-install your script. To verify that it works, refresh that same craigslist.org page and confirm that the contents of delicious.com appear in the console.

Now that I can talk to other domains, let’s consider my ultimate goal: I want to fetch all the recent tags for the current page from Delicious. To do this, I’ll use the Delicious feeds API. That API requires that I send the MD5 hash of my URL rather than the raw URL itself, and to compute an MD5 hash, I’ll need to include an additional jQuery plugin.

There are jQuery plugins for almost everything, including MD5 hashing. This requires another @require and another reinstallation of the script.

// @require	http://plugins.jquery.com/files/jquery.md5.js.txt

Now I can produce the URL that the Delicious feeds API requires, and see on the console what kind of output I’ll receive.

var theUrl = $.md5(window.location.href);

$.get('http://feeds.delicious.com/v2/xml/url' + theUrl, function(xml){
    console.log(xml);
});
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://web.resource.org/cc/" version="2.0">
	<channel>
		<title>Delicious/url/ad1706731c138951229c69f68c6a1c23</title>
		<link>http://delicious.com/url/ad1706731c138951229c69f68c6a1c23</link>
		<description>bookmark history for http://sfbay.craigslist.org/</description>
		<atom:link rel="self" type="application/rss+xml" href="http://feeds.delicious.com/v2/xml/url/ad1706731c138951229c69f68c6a1c23"/>
		<item>
			<title>[from icresource] craigslist - san francisco, east bay, south bay, marin county, california</title>
			<pubDate>Wed, 16 Sep 2009 17:57:38 +0000</pubDate>
			<guid isPermaLink="false">http://delicious.com/url/ad1706731c138951229c69f68c6a1c23#icresource</guid>
			<link>http://sfbay.craigslist.org/</link>
			<dc:creator><![CDATA[icresource]]></dc:creator>
			<comments>http://delicious.com/url/ad1706731c138951229c69f68c6a1c23</comments>
			<wfw:commentRss>http://feeds.delicious.com/v2/rss/url/ad1706731c138951229c69f68c6a1c23</wfw:commentRss>
			<source url="http://feeds.delicious.com/v2/rss/icresource">icresource's bookmarks</source>
			<category domain="http://delicious.com/icresource/">San_Francisco-RC</category>
			<category domain="http://delicious.com/icresource/">East_Bay-RC</category>
			<category domain="http://delicious.com/icresource/">South_Bay-RC</category>
			<category domain="http://delicious.com/icresource/">Marin_County-RC</category>
			<category domain="http://delicious.com/icresource/">events-calendar</category>
			<category domain="http://delicious.com/icresource/">jobs</category>
			<category domain="http://delicious.com/icresource/">housing</category>
			<category domain="http://delicious.com/icresource/">rideshare</category>
			<category domain="http://delicious.com/icresource/">personals</category>
			<category domain="http://delicious.com/icresource/">classifieds,</category>
			<category domain="http://delicious.com/icresource/">forums</category>
		</item>
		<item>
...

I can see from that output that it’s the <category> elements that I’m interested in. We can use the same jQuery selection tools on this XML output that we do on the Document Object Model by wrapping the xml variable in $() and running the find method. And for each <category> element that I find, I’ll add a list item to the bar.

$(xml).find("category").each(function(){
	$("#bar ul").append('<li>' + $(this).text() + '</li>');
});

(In the above code, this refers to the DOM object that we’ve selected from the XML file. By wrapping this in $() I can convert it to a jQuery object and I can use all the jQuery functions that I’m familiar with, like .text().)

At this point, you should be successfully grabbing all the recent tags for the current page from Delicious and inserting them in a bar on the page. And besides the @require lines, your code style should be no different than if you were doing same-origin requests.

Here’s the completed script. If you have any questions, problems, corrections or suggestions, please leave a comment and I’ll respond here.

5 Responses to “Working around the same-origin policy in Greasemonkey”

  1. Brady says:

    Kickass! Works great. Thanks.

  2. Thanks a lot. I really took interest

  3. dedektiflik says:

    thank you very much for this nice article.

  4. aöf sınav says:

    Thank you for this post 🙂 Aöf Sınav