Firefox WebExtension toolbar buttons to toggle sidebars
Opening your own sidebar with a browserAction
button
You can have a browserAction
toolbar button which opens your own sidebar. You can do this by defining both a sidebar_action
and a browser_action
in your manifest.json.
When this question was asked, it wasn't possible to combine both a sidebar_action
and a browser_action
in a manifest.json. I'm not sure when that actually changed, but as of this point (tested on FF71), you can have both.
The following is example code which will do this:
manifest.json:
{
"manifest_version": 2,
"name": "Button opens sidebar",
"description": "Open the sidebar with a toolbar button.",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png",
"default_title": "Open Sidebar"
},
"background": {
"scripts":[
"background.js"
]
},
"sidebar_action": {
"default_icon": "icon.png",
"default_title": "Example Sidebar",
"default_panel": "sidebar.html",
"open_at_install": false
}
}
background.js:
//The manifest.json entry must be a file in your extension, but you can change it to
// an external site in your background.js:
browser.sidebarAction.setPanel({panel: 'https://example.org/'});
//Open the sidebar when the browserAction button is clicked.
browser.browserAction.onClicked.addListener(() => {
browser.sidebarAction.open();
});
sidebar.html
<html>
<body>
<div>Example text</div>
</body>
</html>
Toggling the sidebar open/closed
I tried implementing a naive implementation of the button actually toggling the sidebar:
background.js
browser.browserAction.onClicked.addListener(() => {
browser.sidebarAction.isOpen({}).then((isOpen) => {
if (isOpen) {
browser.sidebarAction.close();
} else {
browser.sidebarAction.open();
}
});
});
Unfortunately, this fails with the error:
Error: sidebarAction.open may only be called from a user input handler
Use a second browserAction onClicked
You can take advantage of the fact that a sidebar that's showing HTML from within your extension is running in the background context, by just adding an additional browserAction.onClicked
listener in the sidebar which closes the sidebar.
background.js
browser.browserAction.onClicked.addListener(() => {
browser.sidebarAction.open();
});
sidebar.js
browser.browserAction.onClicked.addListener(() => {
browser.sidebarAction.close();
});
sidebar.html
<html>
<head>
<script src="sidebar.js"></script>
</head>
<body>
<div>Example text</div>
</body>
</html>
Event listeners are executed in the order they are added, so the one added by the sidebar.js, which closes the sidebar, is run after the one in background.js, which opens the sidebar. Thus, when the sidebar is open, it gets closed upon the browserAction button being clicked.
Can't open the actual Bookmark or History sidebar
What you specifically desire, opening the actual Bookmarks or History sidebar, is not possible with WebExtensions. What you could do is implement similar sidebars yourself.
I did try changing the sidebar URL to a chrome://
URL:
browser.sidebarAction.setPanel({panel: 'chrome://browser/content/aboutDialog.xul'});
Unfortunately, that results in an error:
Error: Access denied for URL chrome://browser/content/aboutDialog.xul
If you desire some way to programmatically open the actual Bookmark or history sidebars, then you will need to file a bug requesting the functionality. Alternately, you can also create a WebExtension Experiment which adds the functionality to the WebExtensions API when the WebExtension Experiment is installed. WebExtension Experiments do not automatically get integrated into Firefox, but there is the possibility that it can be.
Firefox ⩾ 73
Since Firefox 73, you can simply use:
browser.browserAction.onClicked.addListener(() => {
browser.sidebarAction.toggle();
});
Documentation on MDN
Firefox < 73
If you need to support older Firefox versions, especially ESR, you can keep track of what sidebar is open. This is the method I developed for one of my addons and the only one I know that works correctly with several windows.
In your background script:
let openedSidebarWindows = {};
browser.runtime.onConnect.addListener(port => onConnect(port));
browser.browserAction.onClicked.addListener(tab => this.onClick(tab));
function onConnect(port) {
const windowId = parseInt(port.name);
openedSidebarWindows[windowId] = port;
port.onDisconnect.addListener(port => {
delete openedSidebarWindows[parseInt(port.name)];
});
}
function onClick({ windowId }) {
if (openedSidebarWindows[windowId] !== undefined) {
browser.sidebarAction.close();
} else {
browser.sidebarAction.open();
}
}
In your sidebar script:
browser.runtime.connect({ name: windowId.toString() });