-
Notifications
You must be signed in to change notification settings - Fork 280
SubPanel API
(generated by Table of Contents Generator for GitHub Wiki)
Important Note: This very experimental API was initially created to demonstrate: 1) how WebExtensions extension is restricted to provide this kind feature, and 2) only a genuine WebExtensions feature can satisfy such a demand. Please remind that subpanels provided via this API have many restriction, as described at TST Bookmarks Subpanel's distribution page.
You cannot show multiple sidebar panels on Firefox 57 and later. (See also: 1328776 - Provide ability to show multiple sidebar contents parallelly) This is a largely known "regression" of TST2 from legacy versions. A new TST API "SubPanel API" is a workaround to solve this problem. With this feature, you can embed arbitrary contents into TST's sidebar on Tree Style Tab 3.1.0 and later.
Here is a figure to describe relations around a subpanel page:
There is a reference implementation of a subpanel: TST Bookmarks Subpanel. It is a clone of Firefox's bookmarks sidebar for Tree Style Tab's subpanel.
Your addon can register only one subpanel with the register-self
message. Here is an example:
const TST_ID = 'treestyletab@piro.sakura.ne.jp';
async function registerToTST() {
try {
await browser.runtime.sendMessage(TST_ID, {
type: 'register-self',
name: browser.i18n.getMessage('extensionName'),
icons: browser.runtime.getManifest().icons,
subPanel: {
title: 'Panel Name',
url: `moz-extension://${location.host}/path/to/panel.html`
},
listeningTypes: ['wait-for-shutdown']
});
}
catch(_error) {
// TST is not available
}
}
// This is required to remove the subpanel you added on uninstalled.
const promisedShutdown = new Promise((resolve, reject) => {
window.addEventListener('beforeunload', () => resolve(true));
});
browser.runtime.onMessageExternal.addListener((message, sender) => {
switch (sender.id) {
case TST_ID:
switch (message.type) {
// TST is initialized after your addon.
case 'ready':
registerToTST();
break;
// This is required to remove the subpanel you added on uninstalled.
case 'wait-for-shutdown'
return promisedShutdown;
}
break;
}
});
registerToTST(); // Your addon is initialized after TST.
Please note that the sent message has an extra parameter subPanel
. It should be an object containing two properties title
and url
. When the parameter exists, TST automatically loads the URL into an inline frame embedded in TST's sidebar panel.
And, listening of wait-for-shutdown
type notification is required for uninstallation.
Subpanel page is loaded into an inline frame. You can load any script from the document, but those scripts are executed with quite restricted permissions, in particular limited WebExtensions API allowed for content scripts are just available. And you cannot access to the parent frame (TST's contents) due to the same origin policy. So basically you'll need to implement things like:
- The background page: Similar to a server process. It will call WebExtensions API based on requests from the frontend, and returns the result.
- The subpanel page: Similar to a frontend webpage. It handles user interactions and send requests to the background page.
You simply need to use runtime.sendMessage()
. You can receive responses as per usual, like following example:
// in a subpanel page
browser.runtime.sendMessage({ type: 'get-bookmarks' }).then(bookmarks => {
// render contents based on the response
});
browser.runtime.sendMessage({ type: 'get-stored-value', key: 'foo' }).then(data=> {
// ...
});
// in the background page
browser.runtime.onMessage.addListener((message, sender) => {
switch (message.type) {
case 'get-bookmarks':
// This API returns a promise, so you just need return it.
return browser.bookmarks.getTree();
case 'get-stored-value':
// If you can construct a response synchronously,
// you need to wrap it as a promise before returning.
return Promise.resolve(store[message.key]);
}
});
On the other hand, reversed direction messaging is hard a little. You can register listeners for runtime.onMessage
on a subpanel page, but those listeners won't receive any message. Even if you send messages from the background page with runtime.sendMessage()
, you'll just see an exception like: Error: Could not establish connection. Receiving end does not exist.
Instead, you need to use runtime.onConnect
and runtime.sendMessage()
.
First, you register a listener for runtime.onConnect
in the background page:
// in the background page
const connections = new Set();
browser.runtime.onConnect.addListener(port => {
// This callback is executed when a new connection is established.
connections.add(port);
port.onDisconnect.addListener(() => {
// This callback is executed when the client script is unloaded.
connections.delete(port);
});
});
function broadcastMessage(message) {
for (const connection of connections) {
connection.sendMessage(message);
}
}
And, you connect to the background page with runtime.connect()
from a subpanel page:
// in a subpanel page
// connect to the background page
const connection = browser.runtime.connect({
name: `panel:${Date.now()}` // this ID must be unique
});
connection.onMessage.addListener(message => {
// handling of broadcasted messages from the background page
//...
});
Due to security reasons or limitations of WebExtensions API itself, there are some restrictions in your subpanel page:
- Impossible to drag anything from elsewhere to your subpanel page.
event.dataTransfer.getData()
may return nothing in your subpanel page. - Impossible to drag anything from your subpanel page to TST's sidebar. Even if you set data via
event.dataTransfer.setData()
in your subpanel page, TST cannot read them. - Impossible to open native context menu for bookmarks and tabs, because
menus.overrideContext()
is unavailable on your subpanel page.