Skip to content

Instantly share code, notes, and snippets.

@kafene
Created August 29, 2023 14:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kafene/1ad8cc4c60129deb3c4f13b78919d8da to your computer and use it in GitHub Desktop.
Save kafene/1ad8cc4c60129deb3c4f13b78919d8da to your computer and use it in GitHub Desktop.
Tab deque modified (tabs.moveInSuccession)
/* jshint esversion:11 */
// Maps each window ID to an array of its tab IDs
const deques = {};
// New background tabs will be added after the current tab instead of at the end of the deque
let addBackgroundTabsAfterCurrent = false;
// Create deque for this windowId if it does not exist
// Omit hidden tabs, keep discarded tabs at the end.
function ensureDeque(windowId, tabs) {
if (deques[windowId] == null) {
const tabIds = tabs.filter(tab => !tab.hidden && !tab.discarded).map(tab => tab.id);
const discardedTabIds = tabs.filter(tab => tab.discarded).map(tab => tab.id);
deques[windowId] = [...tabIds, ...discardedTabIds];
}
}
function runOnActiveTab(callback) {
browser.tabs.query({active: true, currentWindow: true}).then(tabs => {
for (let tab of tabs) {
callback(tab);
}
}).catch(console.error);
}
function sendTabToEndOfDeque(tab) {
deques[tab.windowId] = deques[tab.windowId].filter(t => t !== tab.id).concat([tab.id]);
browser.tabs.update(deques[tab.windowId][0], {active: true}).then(() => {
return browser.tabs.moveInSuccession(deques[tab.windowId]);
}).catch(console.error);
}
function selectTabFromEndOfDeque(tab) {
const tabId = deques[tab.windowId][deques[tab.windowId].length - 1];
deques[tab.windowId] = [tabId].concat(deques[tab.windowId].filter(t => t !== tabId));
browser.tabs.update(deques[tab.windowId][0], {active: true}).then(() => {
return browser.tabs.moveInSuccession(deques[tab.windowId]);
}).catch(console.error);
}
function selectPreviousTab(currentTab) {
const windowId = currentTab.windowId;
browser.windows.get(windowId, {populate: true}).then(({ tabs }) => {
let prevTab = tabs.find(tab => tab.index === currentTab.index - 1) || tabs[tabs.length - 1];
deques[windowId] = [prevTab.id, ...deques[windowId].filter(v => v !== prevTab.id)];
return browser.tabs.update(prevTab.id, {active: true});
}).catch(console.error);
}
function selectNextTab(currentTab) {
browser.windows.get(tab.windowId, {populate: true}).then(({ tabs }) => {
let nextTab = tabs.find(tab => tab.index === currentTab.index + 1) || tabs[0];
deques[windowId] = [nextTab.id, ...deques[windowId].filter(v => v !== nextTab.id)];
return browser.tabs.update(nextTab.id, {active: true});
}).catch(console.error);
}
function onCommand(command) {
if (command === 'toggle-add-background-tabs-after-current') {
addBackgroundTabsAfterCurrent = !addBackgroundTabsAfterCurrent;
} else if (command === 'send-tab-to-end-of-tabdeque') {
runOnActiveTab(tab => sendTabToEndOfDeque(tab));
} else if (command === 'select-tab-from-end-of-tabdeque') {
runOnActiveTab(tab => selectTabFromEndOfDeque(tab));
} else if (command === 'select-previous-tab') {
runOnActiveTab(tab => selectPreviousTab(tab));
} else if (command === 'select-next-tab') {
runOnActiveTab(tab => selectNextTab(tab));
}
}
function onMenuClicked(info, tab) {
if (info.menuItemId === 'send-tab-to-end-of-tabdeque') {
return runOnActiveTab(tab => sendTabToEndOfDeque(tab));
}
}
function onTabActivated(info) {
const {windowId, tabId} = info;
deques[windowId] = [tabId].concat(deques[windowId].filter(t => t !== tabId));
if (info.previousTabId != null) {
browser.tabs.moveInSuccession([tabId], info.previousTabId).catch(console.error);
}
}
function onTabCreated(tab) {
if (deques[tab.windowId].indexOf(tab.id) === -1) {
if (addBackgroundTabsAfterCurrent) {
deques[tab.windowId].splice(1, 0, tab.id);
} else {
deques[tab.windowId].push(tab.id);
}
}
}
function onTabAttached(tabId, info) {
deques[info.newWindowId].unshift(tabId);
}
function onTabDetached(tabId, info) {
deques[info.windowId] = deques[info.windowId].filter(t => t !== tabId);
}
function onTabRemoved(tabId, info) {
deques[info.windowId] = deques[info.windowId].filter(t => t !== tabId);
}
// Handle tab hiding (experimental)
function onTabHiddenChange(tabId, change, tab) {
if (tab.hidden) {
deques[tab.windowId] = deques[tab.windowId].filter(t => t !== tab.id);
browser.tabs.moveInSuccession(deques[tab.windowId]).catch(console.error);
} else if (tab.active) {
deques[tab.windowId].unshift(tab.id);
} else {
deques[tab.windowId].push(tab.id);
}
}
// Handle tab discards - send discarded tabs to end of deque
function onTabDiscardedChange(tabId, change, tab) {
if (tab.discarded) {
deques[tab.windowId] = deques[tab.windowId].filter(t => t !== tab.id).concat([tab.id]);
browser.tabs.moveInSuccession(deques[tab.windowId]).catch(console.error);
}
}
browser.windows.onCreated.addListener((info) => {
ensureDeque(info.windowId, info.tabs);
});
browser.windows.onRemoved.addListener(windowId => {
delete deques[windowId];
});
// Initialize on startup
browser.windows.getAll({populate: true, windowTypes: ['normal']}).then(windows => {
for (const w of windows) {
ensureDeque(w.id, w.tabs);
}
browser.menus.create({
id: 'send-tab-to-end-of-tabdeque',
title: 'Send tab to end of TabDeque',
contexts: ['tab'],
});
browser.menus.onClicked.addListener(onMenuClicked);
browser.commands.onCommand.addListener(onCommand);
browser.tabs.onCreated.addListener(onTabCreated);
browser.tabs.onRemoved.addListener(onTabRemoved);
browser.tabs.onActivated.addListener(onTabActivated);
browser.tabs.onAttached.addListener(onTabAttached);
browser.tabs.onDetached.addListener(onTabDetached);
browser.tabs.onUpdated.addListener(onTabHiddenChange, {properties: ['hidden']});
browser.tabs.onUpdated.addListener(onTabDiscardedChange, {properties: ['discarded']});
}).catch(console.error);
{
"manifest_version": 2,
"name": "Tab Deque",
"version": "2.1.7",
"description": "A webextension for better tab handling. Inspired by Opera 12.",
"homepage_url": "https://github.com/sblask/webextension-tab-deque",
"browser_specific_settings": {"gecko": {"id": "tabdeque@kafene"}},
"author": "Sebastian Blask",
"background": {"scripts": ["background.js"], "persistent": true},
"icons": {"48": "icon.svg", "96": "icon.svg"},
"permissions": ["menus", "tabs"],
"commands": {
"select-tab-from-end-of-tabdeque": {
"description": "Select tab from end of TabDeque",
"suggested_key": {"default": "Ctrl+Up"}
},
"send-tab-to-end-of-tabdeque": {
"description": "Send tab to end of TabDeque",
"suggested_key": {"default": "Ctrl+Down"}
},
"select-previous-tab": {
"description": "Select previous tab",
"suggested_key": {"default": "Alt+Left"}
},
"select-next-tab": {
"description": "Select next tab",
"suggested_key": {"default": "Alt+Right"}
},
"toggle-add-background-tabs-after-current": {
"description": "Toggle between adding new background tabs to the end of the deque / after the current tab",
"suggested_key": {"default": "Ctrl+Alt+B"}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment