widget
The widget
module provides your add-on with a simple user interface that is
consistent with other add-ons and blends in well with Firefox.
"Widgets" are small pieces of content that live in the Firefox 4 add-on bar. They can be simple icons or complex web pages. You can attach panels to them that open when they're clicked, or you can define a custom click handler to perform some other action, like opening a web page in a tab.
There are a few advantages to using widgets over an ad hoc user interface. First, your users will be accustomed to interacting with add-ons via widgets and the add-on bar. Second, it allows Firefox to treat your interface as a first-class citizen. For example, in the future Firefox may allow the user to drag widgets from the add-on bar to other toolbars. By exposing your interface as a widget, your add-on would automatically inherit such functionality.
Creation and Content
Widgets can contain images or arbitrary web content. You can include this
content inline as a string by using the content
property, or point to content
using a URL with the contentURL
property.
Upon creation, the widget is automatically added to the add-on bar. You can set the width of a widget, but the height is fixed so as to fit in the add-on bar. If the content is an image, it is automatically scaled to be 16x16 pixels.
For example, this widget contains an image, so it looks like a simple icon:
require("widget").Widget({
id: "mozilla-icon",
label: "My Mozilla Widget",
contentURL: "http://www.mozilla.org/favicon.ico"
});
You can make contentURL
point to an HTML or icon file which you have
packaged inside your add-on. Just save the file in your add-on's data
directory, and reference it using the data.url()
method of the
self
module:
var data = require("self").data;
require("widget").Widget({
id: "my-widget",
label: "My Widget",
contentURL: data.url("my-content.html")
});
This widget contains an entire web page:
require("widget").Widget({
id: "hello-display",
label: "My Hello Widget",
content: "Hello!",
width: 50
});
Widgets are quite small by default, so this example used the width
property to
grow it in order to show all the text.
Scripting Widget Content
To interact with the widget's content you need to load a separate script into the panel. In the SDK these scripts are called "content scripts" because they're explicitly used for interacting with web content.
While content scripts can access the content they're attached to, they can't use the SDK's APIs. So implementing a complete solution usually means you have to send messages between the content script and the main add-on code.
-
You can specify one or more content scripts to load into the widget using the
contentScript
orcontentScriptFile
options to theWidget()
constructor. -
You can communicate with the script using either the
postMessage()
API or (preferably, usually) theport
API.
Unless your content script is extremely simple and consists only of a
static string, don't use contentScript
: if you do, you may
have problems getting your add-on approved on AMO.
Instead, keep the script in a separate file and load it using
contentScriptFile
. This makes your code easier to maintain,
secure, debug and review.
For example, suppose we want to implement a media player as an add-on. We could implement the main user interface as a widget hosting an array of buttons to control play/pause/stop functions.
We can then use a content script to listen for clicks on those buttons. But because content scripts can't use the SDK's APIs, we'll want the content script to send messages to the main add-on code, which can then implement the media player functions using the SDK.
The widget's content is specified using HTML like this:
<html> <body> <img src="play.png" id="play-button"> <img src="pause.png" id="pause-button"> <img src="stop.png" id="stop-button"> </body> </html>
We just include three icons, and assign an ID to each one. This HTML file,
and the icon files it references, are saved in the add-on's data
directory.
Next, we write a content script that listens for click events on each icon and sends the corresponding message to the main add-on code:
var play_button = document.getElementById("play-button");
play_button.onclick = function() {
self.port.emit("play");
}
var pause_button = document.getElementById("pause-button");
pause_button.onclick = function() {
self.port.emit("pause");
}
var stop_button = document.getElementById("stop-button");
stop_button.onclick = function() {
self.port.emit("stop");
}
We save this file in the add-on's data
directory as "button-script.js".
Finally. in the add-on's "main.js" file, we create the widget, assign it
the HTML file and the content script, and listen for events from the content
script:
const widgets = require("widget");
const data = require("self").data;
var player = widgets.Widget({
id: "player",
width: 72,
label: "Player",
contentURL: data.url("buttons.html"),
contentScriptFile: data.url("button-script.js")
});
player.port.on("play", function() {
console.log("playing");
});
player.port.on("pause", function() {
console.log("pausing");
});
player.port.on("stop", function() {
console.log("stopping");
});
To learn much more about content scripts, see the Working with Content Scripts guide.
Scripting Trusted Widget Content
Note that the feature described in this section is experimental: we'll
very probably continue to support it, but the name of the addon
property might change in a future release.
We've already seen that you can package HTML files in your add-on's data
directory and use them to define the widget's content. We can call this
"trusted" content, because unlike content loaded from a source outside the
add-on, the add-on author knows exactly what it's doing. To
interact with trusted content you don't need to use content scripts:
you can just include a script from the HTML file in the normal way, using
script
tags.
Like a content script, these scripts can communicate with the add-on code
using the
postMessage()
API or the
port
API.
The crucial difference is that these scripts access the postMessage
and port
objects through the addon
object, whereas content scripts
access them through the self
object.
To show the difference, convert the player
add-on above
to use normal page scripts instead of content scripts.
First, in the content script, change self
to addon
, and wrap it in a
function:
function init() {
var play_button = document.getElementById("play-button");
play_button.onclick = function() {
addon.port.emit("play");
}
var pause_button = document.getElementById("pause-button");
pause_button.onclick = function() {
addon.port.emit("pause");
}
var stop_button = document.getElementById("stop-button");
stop_button.onclick = function() {
addon.port.emit("stop");
}
}
Next, add a script
tag to reference "button-script.js", and
call its init()
function on load:
<html> <head> <script src="button-script.js"></script> </head> <body onLoad="init()"> <img src="play.png" id="play-button"> <img src="pause.png" id="pause-button"> <img src="stop.png" id="stop-button"> </body> </html>
Finally, remove the line attaching the content script from "main.js":
const widgets = require("widget");
const data = require("self").data;
var player = widgets.Widget({
id: "player",
width: 72,
label: "Player",
contentURL: data.url("buttons.html")
});
player.port.emit("init");
player.port.on("play", function() {
console.log("playing");
});
player.port.on("pause", function() {
console.log("pausing");
});
player.port.on("stop", function() {
console.log("stopping");
});
Attaching Panels to Widgets
You can supply a panel to the widget's constructor: if you do this, the panel is automatically displayed when the user clicks the widget.
data = require("self").data
var clockPanel = require("panel").Panel({
width:215,
height:160,
contentURL: data.url("clock.html")
});
require("widget").Widget({
id: "open-clock-btn",
label: "Clock",
contentURL: data.url("History.png"),
panel: clockPanel
});
Note that this is, at the moment, the only way you can attach a panel to a widget.
You must supply the panel in the widget's constructor for it to work. If you assign the panel to the widget after construction, the panel can still be shown but will not be anchored to the widget:
data = require("self").data
var clockPanel = require("panel").Panel({
width:215,
height:160,
contentURL: data.url("clock.html")
});
widget = require("widget").Widget({
id: "open-clock-btn",
label: "Clock",
contentURL: data.url("History.png")
});
widget.panel = clockPanel;
// Will not be anchored
widget.panel.show();
Also, if you try to call panel.show()
inside your widget's click
event
listener, the panel will not be anchored:
data = require("self").data
var clockPanel = require("panel").Panel({
width:215,
height:160,
contentURL: data.url("clock.html")
});
require("widget").Widget({
id: "open-clock-btn",
label: "Clock",
contentURL: data.url("History.png"),
panel: clockPanel,
onClick: function() {
// Will not be anchored
this.panel.show();
}
});
See bug 638142.
Examples
For conciseness, these examples create their content scripts as strings and use
the contentScript
property. In your own add-ons, you will probably want to
create your content scripts in separate files and pass their URLs using the
contentScriptFile
property. See
Working with Content Scripts for more
information.
var widgets = require("widget");
// A basic click-able image widget.
widgets.Widget({
id: "google-link",
label: "Widget with an image and a click handler",
contentURL: "http://www.google.com/favicon.ico",
onClick: function() {
require("tabs").activeTab.url = "http://www.google.com/";
}
});
// A widget that changes display on mouseover.
widgets.Widget({
id: "mouseover-effect",
label: "Widget with changing image on mouseover",
contentURL: "http://www.yahoo.com/favicon.ico",
onMouseover: function() {
this.contentURL = "http://www.bing.com/favicon.ico";
},
onMouseout: function() {
this.contentURL = "http://www.yahoo.com/favicon.ico";
}
});
// A widget that updates content on a timer.
widgets.Widget({
id: "auto-update-widget",
label: "Widget that updates content on a timer",
content: "0",
contentScript: 'setTimeout(function() {' +
' document.body.innerHTML++;' +
'}, 2000)',
contentScriptWhen: "ready"
});
// A widget that loads a random Flickr photo every 5 minutes.
widgets.Widget({
id: "random-flickr",
label: "Random Flickr Photo Widget",
contentURL: "http://www.flickr.com/explore/",
contentScriptWhen: "ready",
contentScript: 'postMessage(document.querySelector(".pc_img").src);' +
'setTimeout(function() {' +
' document.location = "http://www.flickr.com/explore/";' +
'}, 5 * 60 * 1000);',
onMessage: function(imgSrc) {
this.contentURL = imgSrc;
},
onClick: function() {
require("tabs").activeTab.url = this.contentURL;
}
});
// A widget created with a specified width, that grows.
let myWidget = widgets.Widget({
id: "widget-effect",
label: "Wide widget that grows wider on a timer",
content: "I'm getting longer.",
width: 50,
});
require("timers").setInterval(function() {
myWidget.width += 10;
}, 1000);
// A widget communicating bi-directionally with a content script.
let widget = widgets.Widget({
id: "message-test",
label: "Bi-directional communication!",
content: "<foo>bar</foo>",
contentScriptWhen: "ready",
contentScript: 'on("message", function(message) {' +
' alert("Got message: " + message);' +
'});' +
'postMessage("ready");',
onMessage: function(message) {
if (message == "ready")
widget.postMessage("me too");
}
});
API Reference
Classes
Widget
Represents a widget object.
Constructors
Widget(options)
Creates a new widget. The widget is immediately added to the add-on bar.
An object with the following keys:
A required string description of the widget used for accessibility, title bars, and error reporting.
Mandatory string used to identify your widget in order to save its location when the user moves it in the browser. This string has to be unique and must not be changed over time.
An optional string value containing the displayed content of the widget.
It may contain HTML. Widgets must have either the content
property or the
contentURL
property set.
If the content is an image, it is automatically scaled to be 16x16 pixels.
An optional string URL to content to load into the widget. This can be
local content or remote
content, an image or web content. Widgets must have either the content
property or the contentURL
property set.
If the content is an image, it is automatically scaled to be 16x16 pixels.
An optional panel to open when the
user clicks on the widget. Note: If you also register a "click" listener,
it will be called instead of the panel being opened. However, you can show
the panel from the listener by calling this.panel.show()
.
Optional width in pixels of the widget. If not given, a default width is used.
Include this to listen to the widget's click
event.
Include this to listen to the widget's message
event.
Include this to listen to the widget's mouseover
event.
Include this to listen to the widget's mouseout
event.
Include this to listen to the widget's attach
event.
Optional text to show when the user's mouse hovers over the widget. If not
given, the label
is used.
An optional object describing permissions for the content. It should
contain a single key named script
whose value is a boolean that indicates
whether or not to execute script in the content. script
defaults to true.
A local file URL or an array of local file URLs of content scripts to load.
Content scripts specified by this property are loaded before those
specified by the contentScript
property.
A string or an array of strings containing the texts of content scripts to
load. Content scripts specified by this property are loaded after those
specified by the contentScriptFile
property.
When to load the content scripts. This may take one of the following values:
- "start": load content scripts immediately after the document element for the widget is inserted into the DOM, but before the DOM content itself has been loaded
- "ready": load content scripts once DOM content has been loaded, corresponding to the DOMContentLoaded event
- "end": load content scripts once all the content (DOM, JS, CSS, images) for the widget has been loaded, at the time the window.onload event fires
This property is optional and defaults to "end".
Read-only value exposed to content scripts under self.options
property.
Any kind of jsonable value (object, array, string, etc.) can be used here. Optional.
Methods
destroy()
Removes the widget from the add-on bar.
postMessage(data)
Sends a message to the widget's content scripts.
The message to send. The message can be any JSON-serializable value.
on(type, listener)
Registers an event listener with the widget.
The type of event to listen for.
The listener function that handles the event.
removeListener(type, listener)
Unregisters an event listener from the widget.
The type of event for which listener
was registered.
The listener function that was registered.
getView(window)
Retrieve a WidgetView
instance of this widget relative to a browser window.
The BrowserWindow instance to match.
A WidgetView
instance associated with the browser window. Any changes
subsequently applied to this object will only be applied to the widget
attached to that window.
Properties
label : string
The widget's label. Read-only.
content : string
A string containing the widget's content. It can contain HTML. Setting it
updates the widget's appearance immediately. However, if the widget was
created using contentURL
, then this property is meaningless, and setting it
has no effect.
contentURL : string
The URL of content to load into the widget. This can point to
local content loaded from your add-on's "data" directory or remote
content, an image or web content. Setting it updates the widget's appearance
immediately. However, if the widget was created using content
, then this
property is meaningless, and setting it has no effect.
panel : Panel
A panel to open when the user clicks on the widget.
width : number
The widget's width in pixels. Setting it updates the widget's appearance immediately.
tooltip : string
The text of the tooltip that appears when the user hovers over the widget.
allow : object
A object describing permissions for the content. It contains a single key
named script
whose value is a boolean that indicates whether or not to
execute script in the content.
contentScriptFile : string,array
A local file URL or an array of local file URLs of content scripts to load.
contentScript : string,array
A string or an array of strings containing the texts of content scripts to load.
contentScriptWhen : string
When to load the content scripts. This may have one of the following values:
- "start": load content scripts immediately after the document element for the widget is inserted into the DOM, but before the DOM content itself has been loaded
- "ready": load content scripts once DOM content has been loaded, corresponding to the DOMContentLoaded event
- "end": load content scripts once all the content (DOM, JS, CSS, images) for the widget has been loaded, at the time the window.onload event fires
contentScriptOptions : object
Read-only value exposed to content scripts under self.options
property.
Any kind of jsonable value (object, array, string, etc.) can be used here. Optional.
port : EventEmitter
EventEmitter object that allows you to:
- send events to the content script using the
port.emit
function - receive events from the content script using the
port.on
function
See the guide to
communicating using port
for details.
Events
attach
This event is emitted when a new WidgetView
object is created using the
getView()
function.
click
This event is emitted when the widget is clicked.
message
If you listen to this event you can receive message events from content
scripts associated with this widget. When a content script posts a
message using self.postMessage()
, the message is delivered to the add-on
code in the widget's message
event.
Listeners are passed a single argument which is the message posted from the content script. The message can be any JSON-serializable value.
mouseover
This event is emitted when the user moves the mouse over the widget.
mouseout
This event is emitted when the user moves the mouse away from the widget.
WidgetView
Represents a widget instance specific to one browser window.
Anything you do to an instance of this object will only be applied to the instance attached to its browser window: widget instances attached to other browser windows will be unaffected.
By contrast, any changes you make to an instance of the normal Widget
class
will be applied across all browser windows.
This class has all the same methods, attributes and events as the Widget
class except for the getView
method and the attach
event.
In this example WidgetView
is used to display different content for
http
and https
schemes:
// A widget that update its content specifically to each window.
let tabs = require("tabs");
let windows = require("windows").browserWindows;
let widget = widgets.Widget({
id: "window-specific-test",
label: "Widget with content specific to each window",
content: " ",
width: 50
});
// Observe tab switch or document changes in each existing tab:
function updateWidgetState(tab) {
let view = widget.getView(tab.window);
if (!view) return;
// Update widget displayed text:
view.content = tab.url.match(/^https/) ? "Secured" : "Unsafe";
}
tabs.on('ready', updateWidgetState);
tabs.on('activate', updateWidgetState);
Methods
destroy()
Removes the widget view from the add-on bar.
postMessage(data)
Sends a message to the widget view's content scripts.
The message to send. The message can be any JSON-serializable value.
on(type, listener)
Registers an event listener with the widget view.
The type of event to listen for.
The listener function that handles the event.
removeListener(type, listener)
Unregisters an event listener from the widget view.
The type of event for which listener
was registered.
The listener function that was registered.
Properties
label : string
The widget view's label. Read-only.
content : string
A string containing the widget view's content. It can contain HTML.
Setting it updates the widget view's appearance immediately. However,
if the widget view was created using contentURL
, then this property
is meaningless, and setting it has no effect.
contentURL : string
The URL of content to load into the widget. This can point to
local content loaded from your add-on's "data" directory or remote
content, an image or web content. Setting it updates the widget's appearance
immediately. However, if the widget was created using content
, then this
property is meaningless, and setting it has no effect.
panel : Panel
A panel to open when the user clicks on the widget view.
width : number
The widget view's width in pixels. Setting it updates the widget view's appearance immediately.
tooltip : string
The text of the tooltip that appears when the user hovers over the widget view.
allow : object
A object describing permissions for the content. It contains a single key
named script
whose value is a boolean that indicates whether or not to
execute script in the content.
contentScriptFile : string,array
A local file URL or an array of local file URLs of content scripts to load.
contentScript : string,array
A string or an array of strings containing the texts of content scripts to load.
contentScriptWhen : string
When to load the content scripts. This may have one of the following values:
- "start": load content scripts immediately after the document element for the widget view is inserted into the DOM, but before the DOM content itself has been loaded
- "ready": load content scripts once DOM content has been loaded, corresponding to the DOMContentLoaded event
- "end": load content scripts once all the content (DOM, JS, CSS, images) for the widget view has been loaded, at the time the window.onload event fires
contentScriptOptions : object
Read-only value exposed to content scripts under self.options
property.
Any kind of jsonable value (object, array, string, etc.) can be used here. Optional.
port : EventEmitter
EventEmitter object that allows you to:
- send events to the content script using the
port.emit
function - receive events from the content script using the
port.on
See the guide to
communicating using port
for details.
Events
detach
The detach
event is fired when the widget view is removed from its related
window.
This can occur if the window is closed, Firefox exits, or the add-on is
disabled.
click
This event is emitted when the widget view is clicked.
message
If you listen to this event you can receive message events from content
scripts associated with this widget view. When a content script posts a
message using self.postMessage()
, the message is delivered to the add-on
code in the widget view's message
event.
Listeners are passed a single argument which is the message posted from the content script. The message can be any JSON-serializable value.
mouseover
This event is emitted when the user moves the mouse over the widget view.
mouseout
This event is emitted when the user moves the mouse away from the widget view.