Skip to content

Fake Context Menu API

YUKI "Piro" Hiroshi edited this page Jun 9, 2021 · 13 revisions

(generated by Table of Contents Generator for GitHub Wiki)

Important notes

  • On Firefox 64 and later, extra commands in the native tab context menu added by other addons are automatically imported to the tab context menu of TST's sidebar. You addon author don't need to do any special changes to integrate your addon to TST. There is a blog post describing technical details.
    • The fake context menu is still available via a Ctrl-right-click on the sidebar, and it also appears when another addon Multiple Tab Handler triggers it after a tab-drag-selection action.
  • All API message types were prefixed with fake- like fake-contextMenu-create, but now they are unprefixed on TST 3.5.4 and later. Both prefixed and unprefixed versions are supported for backward compatibility, but I recommend you to use only unprefixed types.
  • This document assumes that you have experienced to develop one ore mroe Firefox extensions. If you've never done it, I recommend you to see the basic document for extensions authors.

Extra context menu items on tabs

On Firefox 63 and older, due to limitations of WebExtensions APIs, context menu on tabs is not available on custom tab bar in the sidebar panel. (See also bug 1280347, bug 1376251, and bug 1396031.) Thus TST 2.0 provides its custom context menu in the sidebar. Custom menu items added by browser.menus.create() don't appear in the menu because it is just a fake. Until any of bugs I listed above become fixed, your addon need to register its custom menu item to TST's fake context menu manually.

TST's fake context menu APIs are designed as a subset of the menus API of WebExtensions. You can support both Firefox's native menu and TST's fake menu with small changes.

Add new item to the context menu on tabs

New menu items can be added by a contextMenu-create message. It requires a parameter named params, formatted as a createProperties for the browser.menus.create(). You can share and reuse parameters for your context menu to both Firefox's menu and TST's menu. For example:

const TST_ID = '[email protected]';

let params = {
  id, type, title, parentId,
  contexts: ['page', 'tab']
};
await browser.menus.create(params);
await browser.runtime.sendMessage(TST_ID, {
  type: 'contextMenu-create',
  params
}).catch(error => { /* TST is not available */ });

Only following parameters are available:

  • contexts (only tabs works for TST)
  • documentUrlPatterns
  • id
  • parentId
  • title
  • type (normal, checkbox, radio or separator. checkbox and radio are available on TST 2.4.25 and later.)
  • enabled (on TST 2.4.18 and later)
  • icons (on TST 2.4.18 and later)
  • viewTypes (on TST 3.5.4 and alter)

Both command and onclick are not supported by TST, so you need to use another API similar to browser.menus.onClicked to do something for clicked menu items.

On TST 3.0.12 and later, added menu items won't be shown on a private window if your addon is not allowed for private windows.

The parameter viewTypes is just for top level context menu items on your subpanel on TST 3.5.4 and later.

Update existing item

An existing (already created) menu item can be updated by a contextMenu-update message. It requires a parameter named params, an array of parameters for the browser.menus.update(). You can share and reuse parameters for your context menu to both Firefox's menu and TST's menu. For example:

let params = [
  id,
  { title: "Updated Title" }
];
await browser.menus.update(...params);
await browser.runtime.sendMessage(TST_ID, {
  type: 'contextMenu-update',
  params
}).catch(error => { /* TST is not available */ });

On TST 2.4.25 or later, you can listen contextMenu-shown and contextMenu-hidden messages coressponding to browser.menus.onShown and browser.menus.onHidden, if you list them at the listeningTypes parameter for the register-self message. Please note that there is a difference: tabs delivered to listeners are tree items, not tabs.Tab objects. Here is an example:

let onMenuShown = async (info, tab) => {
  // The `tab` is just a tree item, so you need to get full tab information manually.
  tab = tab && await browser.tabs.get(tab.id);
  let params = [
    'togglePinned',
    { checked: tab && tab.pinned }
  ];
  browser.menus.update(...params);
  browser.menus.refresh();
  browser.runtime.sendMessage(TST_ID, {
    type: 'contextMenu-update',
    params
  }).catch(error => { /* TST is not available */ });
};
browser.menus.onShown.addListener(onMenuShown );
browser.runtime.onMessageExternal.addListener((message, sender) => {
  switch (sender.id) {
    case TST_ID:
      switch (message.type) {
        ...
        case 'contextMenu-shown':
          // The `tab` is just a tree item, so you need to get full tab information manually.
          browser.tabs.get(message.tab.id).then(tab => {
            onMenuShown(message.info, tab);
          });
          break;
        ...
      }
      break;
  }
});

On TST 3.0.12 and later, contextMenu-shown and contextMenu-hidden notifications won't be delivered if they happen on a private window and your addon is not allowed for private windows.

Remove existing item

An existing menu item can be removed by a contextMenu-remove message. It requires a parameter named params, a string of a menu item id for the browser.menus.remove(). You can share and reuse parameters for your context menu to both Firefox's menu and TST's menu. For example:

let params = 'command-for-inactive-tab';
await browser.menus.update(params);
await browser.runtime.sendMessage(TST_ID, {
  type: 'contextMenu-remove',
  params
}).catch(error => { /* TST is not available */ });

Remove all existing items

All menu items added by your addon can be removed by a contextMenu-remove-all message, at a time. It doesn't accept any parameter - same to the browser.menus.removeAll(). For example:

await browser.menus.removeAll();
await browser.runtime.sendMessage(TST_ID, {
  type: 'contextMenu-remove-all'
}).catch(error => { /* TST is not available */ });

Handle click event on menu item

When any added context menu is clicked, a contextMenu-click message is notified to your addon via browser.runtime.onMessageExternal. The message has two properties info (same to the first argument info given to the listener of the browser.menus.onClicked) and tab (a tree items corresponding to the context tab) so you can process it by a common listener for browser.menus.onClicked. For example:

let onMenuItemClick = async (info, tab) => {
  // The `tab` is just a tree item, so you need to get full tab information manually.
  tab = tab && await browser.tabs.get(tab.id);
  switch (info.menuItemId) {
    case 'my-command':
      // do something;
      break;
  }
};
browser.menus.onClicked.addListener(onMenuItemClick);
browser.runtime.onMessageExternal.addListener((message, sender) => {
  switch (sender.id) {
    case TST_ID:
      switch (message.type) {
        ...
        case 'contextMenu-click':
          onMenuItemClick(message.info, message.tab);
          break;
        ...
      }
      break;
  }
});

This message will be delivered to your addon even if you don't list this at the listeningTypes parameter for the register-self message.

On TST 3.0.12 and later, this notification won't be delivered if it happens on a private window and your addon is not allowed for private windows.