Monthly Archives: December 2011

Mozilla addons: jQuery Injection / Script File Injection

UPDATE: @ZER0 suggested to use data.url instead of data.load

In my last blog post I described different ways of interaction between content scripts and pages. One of them was to inject inline scripts in the page. Today I describe a way to inject scripts files such as jQuery in a page.

If the value or function you want to insert in the page is not dynamically generated (i.e. it never changes), you’d better put it in its own file. To do that, you simply need to retrieve the url of the script file from the addon code, pass it to the content script and then inject it.

Passing The URL

The script that you want to inject in the page should be stored in the “data” folder, just like a content script. The URL is retrieved with the usual data.url utility and then passed to a content script using “port.emit” / “port.on“.

// in main.js
require("page-mod").PageMod({
  include: "http://example.com/",
  contentScriptWhen: "start",
  contentScriptFile: data.url( "injecter.js" ),
  onAttach: function( worker ) {
    	worker.port.emit( "init", data.url( "toBeInjected.js" ) );
  }
});
// in injecter.js
...
self.port.on("init", function( scriptURL ) {
  // use the URL
  ...
});

Using an “init” event just to pass a value is rather annoying, but a “contentScriptGlobals” should be added to a future version of the SDK to simplify this, see bug 688127.

Injecting the Script

The code of the injecter is pretty straightforward.

// injecter.js
var script = document.createElement( "script" );
script.type = "text/javascript";

self.port.on("init", function( scriptURL ) {
  script.src = scriptURL;
  window.document.body.appendChild( script );
});

If you have a cleaner way to inject a script in a page, I’d love to hear from it!

Mozilla addons: Interactions between Content Scripts and pages

Addons created with Mozilla’s Addon SDK have a secure mean of communication with web pages: Content Scripts. Although the interaction between addons and content scripts is fairly well documented, this doc lacks some details about the range of possible interactions between content scripts and the page itself.

The window Wrapper

Content scripts have access to a window object which is actually only a wrapper around the DOM of the page. The entire DOM API is available, so you can manipulate the page at will, but variables and functions stored in the global scope of the page aren’t available:

// inline script in example.com/index.html
window.test = "value";
// main.js, our addon
require("page-mod").PageMod({
  include: ["*"],
  contentScriptWhen: "end",
  contentScript: "console.log( 'test: ' + window.test );"
});

When loading example.com/index.html, the previous page-mod will display “test: undefined” in the error console. Likewise, it is impossible to create a variable in the content script, and access it in the page.

// main.js, our addon
require("page-mod").PageMod({
  include: ["*"],
  contentScriptWhen: "start",
  contentScript: "window.test = 'value';"
});
// inline script in example.com/index.html
console.log( "test: " + window.test );

Again, the inline script will log “test: undefined“.

The SDK docs also demonstrates how easy it is to listen to mouse events in the page and pass them to the addon:

// content script loaded by the page-mod
contentScript =
  "window.addEventListener('click', function( event ) {"+
  "  self.port.emit('click', event.target.toString());"+
  "});";

Moar Interaction Pliz

Manipulating the DOM and waiting for mouse or key events is good, but what if you want to pass data to your page or receive data from it? what if you absolutely need to create variables or functions in the page?

The wrong way of doing so is to use the undocumented unsafeWindow object instead of the wrapped and secure window one. This completely defeats the security model of the addon SDK, and since it’s an undocumented feature, it could well disappear in a future update of the SDK.

The right way of doing so is to inject inline scripts in the page and/or use customEvents:

Inline Script Injection

Inline scripts are a third level of scripts (where 1O hours are a minute) that can be used to create variables or functions in the global scope:

// Quotes nesting would be a real mess now,
// let's use a separate file loaded as a contentScriptFile
var script = document.createElement( "script" );
script.innerHTML = "var test =" + JSON.stringify( anyValue )+";";
document.body.appendChild( script );

Validation of this code will output warnings, as addons shouldn’t create script tags. But this is still the safest alternative to the unsafeWindow.

customEvents

customEvents are very similar to mouse events, except that they are programmatically triggered, and that they can transport arbitrary (jsonable) data between content scripts and pages, in both directions:

// inline script in example.com/index.html
var evt = document.createEvent("CustomEvent");
evt.initCustomEvent( "transData", true, false, jsonableValue );
window.dispatchEvent(evt);
// in contentScript.js
window.addEventListener( "transData", function( event ) {
  // the data is available in the detail property of the event
  self.port.emit( "transData", event.detail );
});

I had to use both of these technics in my geolocation debugging addon (geo-devtool) to interact with the Google Maps API, and I’m pretty sure there are much more use cases.

PS: I’m willing to propose improvements to the SDK docs on this topic once the content of this blog post is validated by someone in the addon team.

CORS: an insufficient solution for same-origin restrictions

UPDATE2: The main criticism I have received is that people will click “allow” without understanding the security implications of this action. I don’t know yet how to mitigate this risk, but the statu quo is not an option as the problem already exists: one can already develop cross-browser addons able to perform cross-domain requests after a single-click installation.

UPDATE1: I’ve added technical details of how “user enabled CORS” would work.

The same origin policy is the most fundamental principle of security built in web browsers: a web page cannot access the content of websites hosted on other (sub)domains. Developers came up with JSONP, and XHR2 introduced CORS to work around the strictness of this policy. Both mechanisms need to be implemented by API/content providers (Twitter, Facebook, Google Maps, Flickr, …) and leave no power to third party web devs and users. This severely impedes the hackability of the Web.

I want to see the Web succeed at providing a cross-platform (mobile) apps environment, and stand competition with other proprietary environments. I’m thrilled by the progress of WebAPIs lately, and yet, I find myself often stuck when trying to create basic webapps.

Same Origin Restrictions

While experimenting with foreignr, an offline enabled client for Google Maps, I found myself limited by the impossibility to dynamically cache images across different origins. I wrote about it, and ultimately had to write a client/server library to abuse appCache through iframes (see mediaCache).
Recently I started experimenting with a Twitter client and soon hit the limitations of their JSONP API: every request (save public read) have to be sent with an oAuth signature as a custom header. Since JSONP is transported using a <script> tag instead of an XHR object, custom headers aren’t available.

CORS to the rescue?

Both problems would be solved if Twitter and Google Maps added a simple header to their API: Access-Control-Allow-Origin: *. Unfortunately they don’t, and show no sign of willingness to do so. In fact, despite efforts to promote widespread adoption of CORS, only a very limited number of (small) sites implement it. Many websites provide JSONP APIs, but CORS has much more use cases:

  • uploading pictures to an image hosting website (already allowed by imgur.com),
  • storing images or any media locally for offline use,
  • enabling cross-origin oAuth requests,
  • using images or videos as WebGL textures,
  • processing image data inside a canvas element…

Doing any of that without CORS requires to use a server acting as a same-origin proxy to the targeted APIs/resources. What a costly waste. Meanwhile, every single desktop and mobile SDKs allow developers to do such things, even when based on Web technologies such as Blackberry WebWorks, WebOS SDK or PhoneGap.

CORS and beyond

prompt UI of user enabled CORS
Just like we allow or will allow webapps to do such sensitive operations as geolocating the user, or accessing one’s phonebook after obtaining permission, it should be possible to use public features and resources of one particular website on a different origin (i.e. what a user can access without being logged in).
The security implications are important, but one could imagine an opt-out Access-Control-Prevent-Origin: * header, blacklists and other ways to mitigate the risks.

This mechanism would be no different than current XHRs:

// script on http://example.com
var xhr = new XMLHttpRequest();
xhr.open("GET",
  "http://api.twitter.com/1/statuses/home_timeline.json", true);
xhr.setRequestHeader("Authorization", oAuth);
xhr.onreadystatechange = function ( e ) {
  ...
};
xhr.send();

Executing previous code would prompt the user for permission and a request would be sent only if permission is given, otherwise it would fail with a usual cross-origin error. Cookies associated with the target address would not be sent with this request; it would still have to be authorized through oAuth, even if the user is logged in to Twitter.

Moving forward

I can keep on asking API providers to add CORS headers, I will, hoping that one day it will become as widespread as JSONP. But there will always be new APIs that don’t add it immediately, and there will always be companies that don’t listen to their users. And until Web developers have a way to work around the same origin policy, the Web will not be a suitable platform for many apps.

I’d like to hear about other alternatives to CORS and ways to mitigate the risks. If you’ve got an idea, speak up, and let’s move the web forward.