Skip to content

Extra Tab Contents API

YUKI "Piro" Hiroshi edited this page Jul 6, 2021 · 21 revisions

(generated by Table of Contents Generator for GitHub Wiki)

Abstract

Important Note: this document assumes that you have experienced to develop one or more Firefox extensions. If you've never done it, I recommend you to see the basic document for extensions authors.

This feature is available on TST 3.3.7 and later.

TST provides ability to embed arbitrary contents inside tabs and the new tab button via its API. You can provide custom UI elements on TST's tabs - icons, buttons, thumbnails, and more.

(screenshot)

There are some example usecases made by me:

How to insert extra contents to tabs

You can set extra contents for a tab with a message with the type set-extra-tab-contents. For example:

const TST_ID = 'treestyletab@piro.sakura.ne.jp';

function insertContents(tabId) {
  browser.runtime.sendMessage(TST_ID, {
    type:     'set-extra-tab-contents',
    id:       tabId,
    contents: `<button id="button" part="button">foo</button>`
  });
}

Parameters are:

  • id: Integer, ID of the tab.
  • contents (optional): String, HTML source of extra contents. Dangerous contents are automatically sanitized. If you specify null contents, previous contents will be cleared.
  • style (optional): String, CSS style definitions for inserted contents.
  • place (optional): String, "front" (default value), "behind", or "indent" (available at TST 3.8.5 and later).

If you set different contents to a same tab, existing contents are updated to new one. For more better performance and experience, there are some hints:

  • TST tries to apply minimum changes to existing DOM based on a diff-based DOM updater, so you can apply animation effects to contents with CSS transitions easily.
  • If you give an identifier for each element via the id attribute, it will help TST to apply changes with less steps.
    • Currently the namespace of id is shared with other helper addons which uses Tab Extra Contents API. So Firefox may report error to the browser console from duplicated ids. To avoid this concern, an alternative attribute anonid (named from anonymous-id) is available.
  • It is recommended to keep DOM structure, change only attributes and text contents for more performance.

Insert extra tab contents to all tabs automatically

// For all existing tabs in currently shown sidebars
browser.tabs.query({}).then(tabs => {
  for (const tab of tabs) {
    insertContents(tab.id);
  }
});

// For new tabs opened after initialized
browser.tabs.onCreated.addListener(tab => {
  insertContents(tab.id);
});

// For existing tabs, after the sidebar is shown
async function registerToTST() {
  try {
    await browser.runtime.sendMessage(TST_ID, {
      type: 'register-self',
      name: browser.i18n.getMessage('extensionName'),
      icons: browser.runtime.getManifest().icons,
      listeningTypes: ['sidebar-show']
    });
  }
  catch(e) {
    // TST is not available
  }
}
registerToTST();

browser.runtime.onMessageExternal.addListener((message, sender) => {
  if (sender.id != TST_ID)
    return;

  switch (message.type) {
    case 'sidebar-show':
      browser.tabs.query({ windowId: message.windowId }).then(tabs => {
        for (const tab of tabs) {
          insertContents(tab.id);
        }
      });
      break;
  }
});

Styling extra tab contents

Extra contents are inserted to tabs under a shadow root. The container element generated by TST always has a fixed part name container, and TST appends a common part name extra-contents-by-(sanitized addon id) to all inserted elements originally with their own part attribute. For example, if you set <button id="button" part="button">foo</button> as the contents, it will become:

<span part="extra-contents-by-(sanitized addon id) container">
  <button id="button" part="button extra-contents-by-(sanitized addon id)">
    foo
  </button>
</span>

Such shadow DOM elements won't be styled with regular CSS applied to the document, thus there are two methods to style extra tab contents.

Styling with CSS Shadow Parts

CSS Shadow Parts, available on Firefox 72 and later, is the recommended way. You can use ::part() pseudo element to specify elements under shadow root, for example:

async function registerToTST() {
  try {
    await browser.runtime.sendMessage(TST_ID, {
      type: 'register-self',
      ...
      style: `
        ::part(%EXTRA_CONTENTS_PART% container) {
          background: ThreeDFace;
          border: 1px solid ThreeDDarkShadow;
          color: ButtonText;
        }

        ::part(%EXTRA_CONTENTS_PART% button) {
          background: transparent;
          border: none;
        }

        tab-item.active ::part(%EXTRA_CONTENTS_PART% button) {
          background: InactiveCaption;
          color: InactiveCaptionText;
        }

        tab-item.active ::part(%EXTRA_CONTENTS_PART% button):hover {
          background: ActiveCaption;
          color: CaptionText;
        }
      `
    });
  }
  catch(e) {
  }
}

A placeholder %EXTRA_CONTENTS_PART% is available: it will be replaced to the common part name (extra-contents-by-(sanitized addon id)) automatically.

Styling with <style> element

On old versions of Firefox without CSS Shadow Parts support, you need to use a different way: style parameter for each contents. For exmaple:

browser.runtime.sendMessage(TST_ID, {
  type:  'set-extra-tab-contents',
  ...
  style: `
    [part~="%EXTRA_CONTENTS_PART%"][part~="container"] {
      background: ThreeDFace;
      border: 1px solid ThreeDDarkShadow;
      color: ButtonText;
    }

    [part~="%EXTRA_CONTENTS_PART%"][part~="button"] {
      background: transparent;
      border: none;
    }
  `
});

It will generates custom <style> element for each shadow root, so it may decrease system performance.

Styling with custom user styles

With specifying the calculated common part name with the format extra-contents-by-(sanitized addon id), you can customize styling of shadow DOM contents. Sanitizing is done with a rule: replacing all unsafe characters except alphabets, numbers, hyphen and underscore with _. For example, if the id is tst-active-tab-in-collapsed-tree@piro.sakura.ne.jp, unsafe characters @ and . are replaced then the result becomes extra-contents-by-tst-active-tab-in-collapsed-tree_piro_sakura_ne_jp.

::part(extra-contents-by-tst-active-tab-in-collapsed-tree_piro_sakura_ne_jp tab-container) {
  left: 2em;
}

Handle events on extra contents

Notification messages with types tab-mousedown, tab-mouseup, tab-clicked and tab-dblclicked will have an extra property originalTarget (string, the source of the element which the user operated on) when those actions happen on any extra tab contents.

async function registerToTST() {
  try {
    await browser.runtime.sendMessage(TST_ID, {
      type: 'register-self',
      name: browser.i18n.getMessage('extensionName'),
      icons: browser.runtime.getManifest().icons,
      listeningTypes: [..., 'tab-mousedown', 'tab-dblclicked']
    });
  }
  catch(e) {
    // TST is not available
  }
}
registerToTST();

browser.runtime.onMessageExternal.addListener((message, sender) => {
  if (sender.id != TST_ID)
    return;

  switch (message.type) {
    ...
    case 'tab-mousedown':
    case 'tab-dbclicked':
      if (message.originalTarget) {
        console.log(message.originalTarget); // => "<button>foo</button>"
        return Promise.resolve(true); // cancel default event handling of TST
      }
      break;
  }
});

Provide drag data

You can make inserted extra tab contents draggable, with draggable and data-drag-data attributes.

Simple case: just single drag data

Here is an example to provide a plain text data as the drag data:

<span draggable="true"
      data-drag-data='{ "type":          "text/plain",
                        "data":          "http://example.com/",
                        "effectAllowed": "copyMove" }'>
  Foo
</span>

The value of the data-drag-data attribute should be a JSON string. It should have following properties:

  • type: String, the flavor type of the drag data.
  • data: String, the data to be dragged. Currently just a single string is supported.
  • effectAllowed (optional): String to be set for the dataTransfer.effectAllowed of the drag event, copy by default.

You can give multiple types data as an array. For example:

<span draggable="true"
      data-drag-data='[
        { "type": "text/x-moz-url",
          "data": "http://example.com/\nExample Link" }
        { "type": "text/plain",
          "data": "http://example.com/" }
      ]'>
  Foo
</span>

There is a special type tab, providing draggable data for TST's tab (tree node). tab-type drag data only has type and data properties, and the data is an object with some more properties. Here is an example:

<span part="tab"
      draggable="true"
      data-drag-data='{ "type": "tab",
                        "data": { "id": 10 } }'>
  Foo
</span>

Here is the list of available attributes of the data object:

  • id: Integer, the ID of the tab.
  • asTree (optional): Boolean, false by default. true means that descendant tabs are dragged together, false means that only an individual tab is dragged.
  • allowDetach (optional): Boolean, false by default. true means that dragged tabs are detached from the window when they are dropped outside the sidebar area.
  • allowLink (optional): Boolean, false by default. true means that links or bookmarks are created from dragged tabs when they are dropped outside the sidebar area (bookmarks toolbar, text input field, or others).

Complex case: different drag data for each action

You can provide different drag data for actions with specific modifier keys. For example:

<span draggable="true"
      data-drag-data='{
        "default":    [{ "type": "text/x-moz-url",
                         "data": "http://example.com/\nExample Link" },
                       { "type": "text/plain",
                         "data": "http://example.com/" }],
        "Shift":      { "type": "text/plain",
                        "data": "http://example.com/shifted" },
        "Ctrl+Shift": { "type": "text/plain",
                        "data": "http://example.com/new" }
      }'>
  Foo
</span>

Override context menu

You can override the context menu on an extra tab contents.

  • If you define an element with data-tab-id and the value is a valid tab ID, a tab context menu will be shown on the element. For example: <button data-tab-id="10">Reload</button>
  • If you define an element with data-bookmark-id and the value is a valid bookmark ID, a bookmark context menu will be shown on the element. For example: <button data-bookmark-id="aabbccdd">Bookmark</button>

How to clear extra contents from tabs

You can clear your extra contents from a tab at arbitrary timing, with a message with the type clear-extra-tab-contents. For example:

function clearContents(tabId) {
  browser.runtime.sendMessage(TST_ID, {
    type: 'clear-extra-tab-contents',
    id:   tabId
  });
}

Parameters are:

  • id: Integer, ID of the tab.

This will clear both front and behind contents. If you need to clear only one of them, you need to call set-extra-tab-contents with null contents.

Clear all extra contents from all tabs

You can clear all your extra contents from all tabs, with a message with the type clear-all-extra-tab-contents. For example:

browser.runtime.sendMessage(TST_ID, {
  type: 'clear-all-extra-tab-contents'
});

Extra contents in the new tab button

TST 3.8.2 or later supports extra contents in the new tab button.

How to insert extra contents to tabs

You can set extra contents for new tab button with a message with the type set-extra-new-tab-button-contents. For example:

function insertNewTabButtonContents(tabId) {
  browser.runtime.sendMessage(TST_ID, {
    type:     'set-extra-new-tab-button-contents',
    contents: `<button id="button-in-new-tab-button"
                       part="button-in-new-tab-button"
                      >bar</button>`
  });
}

Parameters are:

  • contents (optional): String, HTML source of extra contents. Dangerous contents are automatically sanitized. If you specify null contents, previous contents will be cleared.
  • style (optional): String, CSS style definitions for inserted contents.

If you set different contents, existing contents are updated to new one. For more better performance and experience, there are some hints:

  • TST tries to apply minimum changes to existing DOM based on a diff-based DOM updater, so you can apply animation effects to contents with CSS transitions easily.
  • If you give an identifier for each element via the id attribute, it will help TST to apply changes with less steps.
    • Currently the namespace of id is shared with other helper addons which uses Tab Extra Contents API. So Firefox may report error to the browser console from duplicated ids. To avoid this concern, an alternative attribute anonid (named from anonymous-id) is available.
  • It is recommended to keep DOM structure, change only attributes and text contents for more performance.

Styling and handling events of extra new tab button contents

You can stylize extra new tab button contents with the method same to extra tab contents.

And you also can handle events on extra new tab button contents with a method similar to the one for extra tab contents, You just need to listen notification messages new-tab-button-mousedown, new-tab-button-mouseup, or new-tab-button-clicked instead of tab-* messages.

Clear extra contents from the new tab button

You can clear all your extra contents from the new tab button, with a message with the type clear-extra-new-tab-button-contents. For example:

browser.runtime.sendMessage(TST_ID, {
  type: 'clear-extra-new-tab-button-contents'
});