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.

2 thoughts on “Mozilla addons: Interactions between Content Scripts and pages

  1. Pingback: Mozilla addons: jQuery Injection / Script File Injection | Louis-Rémi

  2. Pingback: Jetpack Project: weekly update for Dec. 13th, 2011 | Mozilla Add-ons Blog

Comments are closed.