Add-on Compatibility for Firefox 29

Jorge Villalobos

23

Firefox 29 will be released on April 29th. Here’s the list of changes that went into this version that can affect add-on compatibility. There is more information available in Firefox 29 for Developers, so you should read that too.

Australis!

This is an actual movie

Yes, this is a joke.

This is a big one. The Australis theme introduces major changes that all add-on developers should be aware of, but it’s nowhere nearly as dramatic as initially thought. We’ve already been blogging about it extensively, so please give these articles a read if you haven’t already:

Notable changes include the removal of the add-on bar and the Firefox button on Windows, the introduction of the menu panel, and an overhaul in the toolbar customization system. Toolbars are no longer hidden for about: pages, there’s no small/large button setting anymore, and tabs are always on top.

Make sure you test your add-on extensively, paying special attention to button customization.

If you notice any problems in your add-ons related to Australis, please file a bug and add it as a dependency to one of these bugs:

  • Add-on issues. This is for add-ons that haven’t been updated yet and are visually broken in Firefox 29 and above.
  • Australis add-on bugs. Use this one if something is broken in your add-on and you believe it’s a bug in the new code.

General

XPCOM

New!

Please let me know in the comments if there’s anything missing or incorrect on these lists. If your add-on breaks on Firefox 29, I’d like to know.

The automatic compatibility validation and upgrade for add-ons on AMO will happen soon, so keep an eye on your email if you have an add-on listed on our site with its compatibility set to Firefox 28.

Changes to unsafeWindow for the Add-on SDK

wbamberg

25

In Firefox 30 the Add-on SDK is making a change to the execution environment for content scripts. While this change will not affect most add-ons, some patterns will no longer work. Specifically, content scripts will no longer be able to use unsafeWindow or window.wrappedJSObject to make JavaScript objects available to content. The more common pattern of accessing objects in the page directly via unsafeWindow will continue to work as expected.

What we’re introducing instead are some new APIs that you can use to share functions and objects with content explicitly. While you’re working on migrating to these new APIs, there’s a mechanism you can use to switch your add-on back to the old behavior as a short-term measure.

Who’s affected?

Add-on SDK-based add-ons that use unsafeWindow or window.wrappedJSObject in content scripts to share JavaScript objects with the scripts loaded by a web page will be broken by this change.

By default, content scripts and the scripts loaded into web pages are insulated from each other. They can both see the DOM, but content scripts can’t access objects defined by page scripts, and page scripts can’t access objects defined by content scripts:

// content-script.js
var button = document.getElementById("show-page-script-var");
button.addEventListener("click", function() {
  // access object defined in page script
  console.log(window.pageScriptObject.greeting);        // undefined
}, false);
 
window.contentScriptObject = {"greeting" : "hello from add-on"};
// page-script.js
var button = document.getElementById("show-content-script-var");
 
button.addEventListener("click", function() {
  // access object defined by content script
  console.log(window.contentScriptObject.greeting);     // undefined
}, false);
 
// define object to be accessed by content script
window.pageScriptObject = {"greeting" : "hello from web page"};

Sometimes a content script wants to break through this insulation, and to do that it can use unsafeWindow (or window.wrappedJSObject, which is identical):

// content-script.js
var button = document.getElementById("show-page-script-var");
button.addEventListener("click", function() {
  // access object defined in page script
  console.log(unsafeWindow.pageScriptObject.greeting);  // "hello from web page"
}, false);
 
unsafeWindow.contentScriptObject = {"greeting" : "hello from add-on"};
// page-script.js
var button = document.getElementById("show-content-script-var");
 
button.addEventListener("click", function() {
  // access object defined by content script
  console.log(window.contentScriptObject.greeting);     // "hello from add-on"
}, false);
 
window.pageScriptObject = {"greeting" : "hello from web page"};

From Firefox 30 onwards, this mechanism won’t work any more to share objects from the content script to the page script:

// content-script.js
var button = document.getElementById("show-page-script-var");
button.addEventListener("click", function() {
  // access object defined in page script
  console.log(unsafeWindow.pageScriptObject.greeting);  // "hello from web page"
}, false);
 
unsafeWindow.contentScriptObject = {"greeting" : "hello from add-on"};
unsafeWindow.contentScriptPrimitive = 42;
// page-script.js
var button = document.getElementById("show-content-script-var");
 
button.addEventListener("click", function() {
  // access primitive defined by content script
  console.log(window.contentScriptPrimitive);            // 42
  // access object defined by content script
  console.log(window.contentScriptObject.greeting);     // undefined
}, false);
 
window.pageScriptObject = {"greeting" : "hello from web page"};

Again, the use of unsafeWindow to access variables defined in the page script is unaffected, so this change is asymmetric. Content scripts can still access page objects using unsafeWindow, but not vice versa.

Also note that the content script will still be able to share primitive values with the page using unsafeWindow, just not objects.

If the page script tries to read a variable defined using unsafeWindow, it will get the value undefined. If it tries to write to such a variable, the browser will throw an exception with this message: “Permission denied to access property [name of the property]“.

The workaround

In the short term, there’s a new option in package.json called “unsafe-content-script” under the “permissions” key. By setting it to true you can revert to the old behavior:

"permissions": {
  "unsafe-content-script": true
}

However, this is only a temporary fix to enable your add-on to keep working while you update to the new APIs. We will deprecate and eventually remove this flag.

The real fix

The real fix is to use three new APIs to share objects and functions with web content: cloneInto(), exportFunction(), and createObjectIn(). All three of these functions are made available to content scripts as globals.

cloneInto()

You can use cloneInto() to clone an object from the content script’s context into the page script’s context. cloneInto() creates a structured clone of the object in the target context, and returns a reference to the clone. You can then assign that to a property of the target window, and the page script can access it:

// content-script.js
contentScriptObject = {"greeting" : "hello from add-on"};
unsafeWindow.contentScriptObject = cloneInto(contentScriptObject, unsafeWindow);
// page-script.js
var button = document.getElementById("show-content-script-var");
 
button.addEventListener("click", function() {
  // access object defined by content script
  console.log(window.contentScriptObject.greeting);     // "hello from add-on"
}, false);

You can also assign the reference to an object you’ve created using createObjectIn():

var foo = createObjectIn(unsafeWindow, {defineAs: "foo"});
var contentScriptObject = {"greeting" : "hello from add-on"};
unsafeWindow.foo.contentScriptObject = cloneInto(contentScriptObject, unsafeWindow);
// page-script.js
var button = document.getElementById("show-content-script-var");
 
button.addEventListener("click", function() {
  // access object defined by content script
  console.log(window.foo.contentScriptObject.greeting); // "hello from add-on"
}, false);

exportFunction()

You can use exportFunction() to expose a function from a content script to a page script. In this way a function defined in a content script can be called by the page script. Any non-native arguments, and the return value, are cloned into the page script’s context.

You can export the function to the target’s window object:

// content-script.js
var salutation = "hello, ";
function greetme(user) {
  return salutation + user;
}
 
exportFunction(greetme, unsafeWindow, {defineAs: "greetme"});
// page-script.js
var button = document.getElementById("call-content-script-function");
 
button.addEventListener("click", function() {
  console.log(window.greetme("page script"));           // "hello, page script"
}, false);

You can also export the function to an object you’ve created in the page context using createObjectIn(). This code creates a new object in the page’s context called foo, and attaches the greetme() function to that object:

// content-script.js
var salutation = "hello, ";
function greetme(user) {
  return salutation + user;
}
 
var foo = createObjectIn(unsafeWindow, {defineAs: "foo"});
exportFunction(greetme, foo, {defineAs: "greetme"});
// page-script.js
var button = document.getElementById("call-content-script-function");
 
button.addEventListener("click", function() {
  console.log(window.foo.greetme("page script"));
}, false);

createObjectIn()

You can use createObjectIn() to create a new object in the page script’s context. You can then export objects and functions to that object, instead of the target scope’s global window. See the previous sections for some examples of this.

Structured cloning

These functions use the structured clone algorithm to clone objects into the page context. This is more capable than JSON serialization, but still has some serious limitations. Most notably, you can’t clone functions, so you can’t export functions that take functions as arguments (such as callbacks) and functions that return functions. However, we’re working on adding better support at least for common patterns like callbacks.

More details

To learn more about the new APIs see the reference documentation:

If you have any questions, please ask in the Jetpack mailing list or the #jetpack channel on irc.mozilla.org.

Add-on Debugger now in Firefox Nightly

Dave Townsend

16

Since the dawn of Firefox debugging add-ons has been a task fraught with peril. The old JavaScript Debugger existed but hasn’t received the care and attention it needed. Most of us resorted to logging with dump statements to try to figure out why things were going wrong.

That is going to change. Recently landed in Firefox Nightly is the new Add-on Debugger. A full JavaScript debugger targeted at add-ons. It behaves like the developer tools debugger for webpages and the browser toolbox but will show the scripts that your add-on uses to make it easy to find where things are going wrong.

The feature is still experimental and we need your help to make it shippable. To use it you need to turn on two options. Open the developer tools, go to the settings and enable chrome debugging and remote debugging. Once they are on you can find debug buttons for add-ons in the Add-ons Manager. We support debugging any restartless add-on, older style add-ons won’t be supported for now. We want to focus on making the experience as awesome as possible for the newer style.

When the debugger opens you’ll see a list of all the scripts from your add-on that are currently in use. If your add-on uses the Add-on SDK then the modules you use will show up in their own section as well. As you might expect you can set breakpoints and step through code. You won’t see all of your code immediately, only scripts that are in use. When new code is loaded it should show up. We’ve put together a short screencast showing enabling the debugger and a few things you can do with it:

Many thanks to our intern on the SDK team last year, Mike Hordecki, for doing the bulk of this work assisted by Eddy Bruel. Also thanks to Jordan Santell for getting it landed.

As I said we need your help to make this better. Please file bugs anywhere you see problems, either scripts not appearing in the debugger that you expect to be there or being unable to use the debugger properly. For now the debugger is the only part of the toolbox that you’ll see. In the future we want to add an add-on specific console as well as start showing the other developer tools where they make sense for add-on developers. Let us know what you want to see either in comments here or by filing bug reports in the Firefox Developer Tools Debugger component.

April Featured Add-ons

Amy Tsay

11

Pick of the Month: S3.Download Statusbar

View and manage downloads from a tidy statusbar. Despite its compact size, S3. Download Statusbar packs in more useful features than the standard download window. The fully customizable interface auto-hides when not in use, allowing full control without interruption.

Great add-on, a must have. The checksum verification is a blessing. Also thanks for good and fast support!”

Get S3.Download Statusbar »

125114

Featured Complete Theme

FT GraphiteGlow
Smooth graphite theme for Firefox. Get it now »

116878

Also Featured

Web2PDF Converter
Convert web pages into PDF files directly from your browser. Get it now »

Free Memory
An add-on to perform free-memory operations without visiting about:memory. Get it now »

Redirect Cleaner
Cleans up redirects from links. Get it now »

Nominate your favorite add-ons!

Featured add-ons are selected by a community board made up of add-on developers, users, and fans. Board members change every six months, so there’s always an opportunity to participate. If you’re interested, please follow this blog and look out for invitations that we send out every six months.

If you’d like to nominate an add-on for featuring, please send it to amo-featured@mozilla.org for the board’s consideration. We welcome you to submit your own add-on!

Announcing Add-on SDK 1.16!

Jeff Griffiths

7

I’m pleased to announce the immediate availability of Add-on SDK 1.16! You can download the SDK directly in either zip or tarball format. This is a minor release meant to provide compatibility with Firefox 29 and a smooth development experience when using the new Australis UI apis I featured in my previous post. In addition, this new release fixes the following issues:

  • Bug 944951 – “bootstrap.js must remove the added resource: URIs on unload”
  • Bug 958609 – “Add-on SDK 1.15 incompatible with Python 2.7.6″
  • Bug 963401 – “Console is flooded with javascript strict warnings” sorry for any confusion, this change did not make it to release.

The AMO validator tool is scheduled to be updated to support extensions created with SDK 1.16 next Tuesday April 1st (no, really). As always, we’d love to hear from you about your experiences with this release. You can contact us in a variety of ways:

post to our discussion group
chat with us on irc.mozilla.org #jetpack
report a bug
check out the source and contribute bug fixes, enhancements, or documentation

For more information on the Jetpack Project check out our wiki.

Add-ons Update – Week of 2014/03/26

Jorge Villalobos

3

I post these updates every 3 weeks to inform add-on developers about the status of the review queues, add-on compatibility, and other happenings in the add-ons world.

The Review Queues

  • Most nominations for full review are taking less than 2 weeks to review.
  • 77 nominations in the queue awaiting review.
  • Most updates are being reviewed within 10 days.
  • 65 updates in the queue awaiting review.
  • Most preliminary reviews are being reviewed within 1 week.
  • 83 preliminary review submissions in the queue awaiting review.

If you’re an add-on developer and would like to see add-ons reviewed faster, please consider joining us. Add-on reviewers get invited to Mozilla events and earn cool gear with their work. Visit our wiki page for more information.

Firefox 28 Compatibility

The add-on compatibility update for Firefox 28 is up, and the compatibility bump for AMO add-ons was run.

Firefox 29 Compatibility (Australis!)

This is a big one. The add-on compatibility update post is still pending, but we already have a few posts explaining some of what’s new:

More on this coming up. As usual we recommend using the Aurora and Beta branches to test your add-ons ahead of time.

Finally, we’re running a contest! If you’re an extension or complete theme developer, we have 3 categories you can compete in to win a Firefox OS phone and other goodies. Please visit the contest page for more information.

New Add-on SDK Australis UI features in Firefox 29

Jeff Griffiths

28

Update: the ability to attach a panel to a button, and the ability to include buttons in toolbars, are not included in Firefox 29 (the current Beta release). Both these features are in Firefox 30 (the current Aurora release). Note that it is still possible to add frames to toolbars in Firefox 29.


With Australis entering Beta, I’m pleased to introduce four new APIs for building your add-on’s user interface: the action button, the toggle button, the toolbar, and the frame.

Buttons, buttons everywhere

First up, we have two APIs for adding buttons to Firefox: action buttons and toggle buttons.

You give action buttons an icon, a label, and a click handler:

var ui = require("sdk/ui");
 
var action_button = ui.ActionButton({
  id: "my-button",
  label: "Action Button!",
  icon: "./icon.png",
  onClick: function(state) {
    console.log("You clicked '" + state.label + "'");
  }
});

Unless you add them to a toolbar of your own, they appear in the toolbar at the top right of the browser window:

action-button-toolbar

Toggle buttons are just the same, except they’re meant to represent an on/off state, like a checkbox. So they have a “checked” property which is toggled when the user clicks the button, and the icon gets a “pressed” look when the button is checked:

var ui = require("sdk/ui");
 
var toggle_button = ui.ToggleButton({
  id: "my-button",
  label: "my button",
  icon: "./icon.png",
  onChange: function(state) {
    console.log(state.label + " checked state: " + state.checked);
  }
});

You can also attach panels to toggle buttons, by passing the button into the panel’s constructor or its show() method.

panel-attach

Toolbars and frames

The toolbar API lets you create more complex persistent user interfaces. You can add buttons to a toolbar, and can also add frames, which are essentially iframes with message passing between page scripts and your main add-on code. You create a frame by specifying the local HTML content to load into it.

Here’s an example that defines a toolbar with a single frame and three action buttons:

var ui = require('sdk/ui');
 
var previous = ui.ActionButton({
  id: "previous",
  label: "previous",
  icon: "./icons/16-back.png"
});
 
var next = ui.ActionButton({
  id: "next",
  label: "next",
  icon: "./icons/16-next.png"
});
 
var play = ui.ActionButton({
  id: "play",
  label: "play",
  icon: "./icons/16-play.png"
});
 
var frame = ui.Frame({
  url: "./frame-player.html"
});
 
var toolbar = ui.Toolbar({
  title: "Player",
  items: [previous, next, play, frame]
});

Toolbars occupy a complete strip of the browser window, just above the content window:

toolbar2

Learning more

There’s full reference documentation on all these APIs on MDN, and I’ve created an example add-on to check out.

Australis for Add-on Developers – Part 2: CustomizableUI

Jorge Villalobos

41

In the previous Australis post, I covered what you would need to change in your extension to adapt to the new toolbars in the Australis theme. In this installment I’ll cover a very exciting addition that facilitates the creation of toolbar widgets: the CustomizableUI module.

While the size of the module and its docs may seem overwhelming, in reality it’s very easy to use if you just intend to add a button to one of the customizable areas in Firefox. I made a couple of simple extensions to try it out and demonstrate how it works.

The demos only work on Australis, so to try them out you need Firefox 29 (currently on Aurora) or Firefox 30 (Nightly).

1. Hello Demo

XPI. Code on Github.

For the first demo I went with the classic “Hello World” example. I also wanted to make it a restartless extension, and this is where I think CustomizableUI really shines. Creating a restartless add-on that adds a button to the toolbar has never been this easy.

First you need to import the module, of course:

Cu.import("resource:///modules/CustomizableUI.jsm");

Then you create the widget in your startup process using the create widget function:

CustomizableUI.createWidget(
  { id : "aus-hello-button",
    defaultArea : CustomizableUI.AREA_NAVBAR,
    label : "Hello Button",
    tooltiptext : "Hello!",
    onCommand : function(aEvent) {
      let win = aEvent.target.ownerDocument.defaultView;
 
      win.alert("Hello!");
    }
  });

The ID of the widget is the same ID that the button will have in the DOM. The default area is where the widget will be added, in this case the main toolbar. The list of possible areas can be found in the doc. You’ll usually want either AREA_NAVBAR or AREA_PANEL for the menu panel.

The command code is straightforward: use the event parameter to get to the browser window and then show a simple alert.

And then of course you want to clean it up in the shutdown process:

CustomizableUI.destroyWidget("aus-hello-button");

With only this you already have a functional toolbar button add-on. The button can be customized and moved around, and its position will be remembered. It works wherever you put it. But it’s missing all CSS, so it doesn’t have an icon yet (it’s also missing localization, but that can be added with a string bundle).

For a regular add-on that requires a restart, adding the styles is simple enough. Create a chrome.manifest file, declare a skin and then a style that applies to browser.xul:

style chrome://browser/content/browser.xul chrome://aus-hello/skin/toolbar.css

For a restartless add-on this is a little trickier because style manifest declarations aren’t supported. You still need the manifest and the skin, but the style needs to be injected, in this case using the stylesheet service:

let io =
  Cc["@mozilla.org/network/io-service;1"].
    getService(Ci.nsIIOService);
 
this._ss =
  Cc["@mozilla.org/content/style-sheet-service;1"].
    getService(Ci.nsIStyleSheetService);
this._uri = io.newURI("chrome://aus-hello/skin/toolbar.css", null, null);
this._ss.loadAndRegisterSheet(this._uri, this._ss.USER_SHEET);

This loads the stylesheet for all documents, so it’s prudent to limit the styles to the document we want in the CSS file:

@-moz-document url("chrome://browser/content/browser.xul") {
/* Code goes here*/
}

And then clean everything up:

if (this._ss.sheetRegistered(this._uri, this._ss.USER_SHEET)) {
  this._ss.unregisterSheet(this._uri, this._ss.USER_SHEET);
}

Note: loading the stylesheet this way is inefficient. The second demo takes a better approach using the loadSheet function, but it would have required too much additional code for this example.

That’s it! With the 16×16 and 32×32 icons as recommended in part 1, you should have a functional Hello World add-on.

2. View Demo

XPI. Code on Github.

Another type of widget you can create is a view. This is a button with a popup panel when in the toolbar, and a side panel when in the menu panel. The Bookmarks and History widgets in Australis versions are this type of widget.

To create the widget, the code is only slightly different:

CustomizableUI.createWidget(
  { id : "aus-view-button",
    type : "view",
    viewId : "aus-view-panel",
    defaultArea : CustomizableUI.AREA_NAVBAR,
    label : "Hello Button",
    tooltiptext : "Hello!",
    onViewShowing : function (aEvent) {
      // initialize code
    },
    onViewHiding : function (aEvent) {
      // cleanup code
    }
  });

The differences are the type (which defaults to button), the viewId and the onViewShowing and onViewHiding events.

viewId is the ID of a <panelview> that must exist in the browser window DOM. This is very easy to do in a regular extension where you can just create an overlay with the view and whatever content you want in it. For a restartless extension you’ll need a window listener and inject the view and its contents into the DOM for all windows. To make things simple, I decided to use a XUL iframe as the only child element, so I don’t have to build everything dynamically.

let panel = doc.createElement("panelview");
let iframe = doc.createElement("iframe");
 
panel.setAttribute("id", "aus-view-panel");
iframe.setAttribute("id", "aus-view-iframe");
iframe.setAttribute("type", "content");
iframe.setAttribute("src", "chrome://aus-view/content/player.html");
 
panel.appendChild(iframe);
doc.getElementById("PanelUI-multiView").appendChild(panel);

The onViewShowing and onViewHiding events should be used to set up the view before it appears, and to clean it up after it disappears.

This demo is meant to detect links to MP3 or OGG files on web pages and use the <audio> element to play them. So, the code in onViewShowing should scan the currently active page for these links and pass the paths of the audio files to the player inside the iframe. Should be easy, right?

There’s a small problem. When the view is shown, the view node is moved from where you put it to a different part of the DOM. This extraction and reinsertion resets the elements inside the panel. On top of that, this happens after onViewShowing is called. So, manipulating the iframe in onViewShowing is not going to work because all changes will be reset when the view is actually shown. There are a couple of ways of solving this:

  1. Instead of depending on onViewShowing, the code inside the iframe can detect when it is loaded and run the code itself. However, that also means stepping out of that document to obtain the links in the active tab, so it takes a lot of extra code.
  2. Add a timeout to the onViewShowing code so it runs slightly later and when the iframe is available for modifications. This is a hacky solution, but also the simplest, so I went with this one since it’s only a demo.

With that little hack in, the extension works well. You can try it out on Web Archive pages like this one.

Australis view

SDK Add-ons

SDK add-ons developers can use this module, which encapsulates what CustomizableUI does for the rest.

References