Loading Twitter's widgets.js in a Chrome extension

Twitter’s Javascript-based third party offerings include buttons and embedded content, which are handy for putting Twitter content into your website. Unfortunately, they break when included in a Chrome extension, due to the use of protocol-relative URLs. Luckily there’s a way to fix the problem, although it requires a bit of extra code.

There are two concepts fundamental to this problem. The first is the Chrome extension resource address format. For example, if you have a file index.html in your chrome extension, Chrome gives it the address of chrome-extension://foobarbaz/index.html, where foobarbaz is an ID number unique to your extension. This address works pretty much like a standard URL - if you were to have a relative link to bar.html in index.html, it would resolve to chrome-extension://foobarbaz/bar.html.

The second concept is that of the protocol-relative URL. This is a mechanism which allows you to specify an URL which inherits its protocol from the current page. For example, if you were to include <script src="//example.com/javascript.js"></script> in a page loaded via HTTPS, then the browser would load https://example.com/javascript.js. In a page loaded via HTTP, the browser would load http://example.com/javascript.js.

The issue, therefore, is that Twitter’s widgets code relies on protocol-relative URLs to load the JavaScript which delivers all the goodies. For example, this code:

<a href="https://twitter.com/share" class="twitter-share-button" data-via="kurrik">Tweet</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>

Will attempt to load chrome-extension://platform.twitter.com/widgets.js when included in a Chrome extension. Naturally this won’t resolve. The obvious workaround is to hardcode https://platform.twitter.com/widgets.js into your script include, but that script includes additional resources in a protocol-relative format, so the breakage just occurs further down the line.

Luckily there’s a WebKit DOM event called beforeload which allows pages to block or modify requests they make. It’s a bit thinly documented - see “blocking unwanted content” here. It’s pretty easy to implement though - listen for the event on the page, find events which match the appropriate chrome-extension:// URLs, then stop the event and rewrite the URLs being loaded. Just make sure to set this up before including the widgets.js script and you should get the events without a problem:

rewrites = [
  [/chrome-extension:\/\/([a-z]+)\.twitter\.com/, 'https://$1.twitter.com'],
  [/chrome-extension:\/\/([a-z]+)\.twimg\.com/, 'https://$1.twimg.com']
];

document.addEventListener('beforeload', function(e){
  for (var i = 0, rule; rule = rewrites[i]; i++) {
    if (rule[0].test(e.url)) {
      e.preventDefault();
      e.stopPropagation();
      e.srcElement.src = e.srcElement.src.replace(rule[0], rule[1]);
      break;
    }
  }
}, true);

Make sure you don’t forget to change your widgets.js include to reference the HTTPS file. Don’t be tempted to use HTTP, either. Since Chrome extensions have privileged access to your browser, it’s rather important to only load HTTPS resources:

<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="https://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>

Finally, the new changes to Chrome Extension security require the use of a Content Security Policy directive to allow third-party components to be embedded in an extension page. You’ll need to make sure your manifest contains the following (I’ve wrapped it for legibility but this should all be on a single line in your manifest):

"content_security_policy":
  "script-src 'self'
              https://platform.twitter.com
              https://cdn.api.twitter.com
              https://syndication.twimg.com;
   object-src 'self'",

You may need to amend this if you include resources from elsewhere on the web.

I’ll note that this technique will be generally applicable for any third-party Javascript libraries which use protocol-relative URLs. It may help open up a lot of new functionality for Chrome extensions.

Comments? In the interest of limiting third-party Javascript running on this site, I've disabled all commenting plugins. If you have feedback, please share it with me on Twitter.

Elsewhere

Twitter (@kurrik) Github (kurrik) Google+ (+kurrik) Linkedin (kurrik)

Tags

arne (10) reviews (9) twitter (7) work (7) chrome (7) extensions (6) games (5) cinemaclub (5) html (4) javascript (3) google (3) go (3) books (3) presentations (3) algorithms (3) internet (2) ludumdare (2) estonia (2) appengine (2) readinglist (1) space (1) product (1) art (1) management (1) recipes (1) http (1)