From 988ec5b77058e08d33af729934a83ba53891a60d Mon Sep 17 00:00:00 2001 From: Fox Kiester Date: Fri, 1 May 2020 17:42:48 -0400 Subject: [PATCH] Add tab group commands Functionality implemented: - :tgroupcreate to create a tab group - :tgroupswitch to switch to or create a tab group - :tgrouplast to switch to the previously active tab group - :tgrouprename to rename the current tab group - :tgroupclose to close all tabs in the current tab group - :tgroupmove to move the current tab to another tab group - :tgroupabort to destroy all tab group information and show all tabs - :tabgroup... aliases for the above commands - Completion for :tgroupswitch and :tgroupmove - Restoring tab group setup on firefox start (seems to just work without any extra code) - Basic mode indicator - Setting for initial urls for to use newly created tab groups (after first) - Setting for whether :tab shows hidden tabs. Edge cases handled - Opening tabs outside of tridactyl mechanisms (e.g. firefox gui, C-t, etc.) - Switching to a hidden tab - Detaching and attaching tabs - Pinned tabs (they can't be hidden; the commands ignore them) - Various other edge cases Fixes #811. --- doc/amo.md | 2 + src/commandline_frame.ts | 2 + src/completions/Tab.ts | 16 +- src/completions/TabGroup.ts | 70 ++++++ src/content.ts | 16 +- src/content/state_content.ts | 3 + src/excmds.ts | 204 ++++++++++++++++- src/lib/config.ts | 17 ++ src/lib/tab_groups.ts | 413 +++++++++++++++++++++++++++++++++++ src/lib/webext.ts | 15 ++ src/manifest.json | 1 + 11 files changed, 752 insertions(+), 7 deletions(-) create mode 100644 src/completions/TabGroup.ts create mode 100644 src/lib/tab_groups.ts diff --git a/doc/amo.md b/doc/amo.md index 0e5d0891..fe3ad034 100644 --- a/doc/amo.md +++ b/doc/amo.md @@ -59,5 +59,7 @@ Since Tridactyl aims to provide all the features Vimperator and Pentadactyl had, - This allows us to use Firefox's built-in find-in-page API, for, for example, allowing you to bind find-next and find-previous to `n` and `N`. - Monitor extension usage and manage themes: - Tridactyl needs this to integrate with and avoid conflicts with other extensions. For example, Tridactyl's contextual identity features use this to cooperate with the Multi-Account Containers extension. +- Hide tabs: + - Tridactyl needs this for tab group commands, which allow associating names with different groups of tabs and showing the tabs from only of those groups at a time. [betas]: https://tridactyl.cmcaine.co.uk/betas/?sort=time&order=desc diff --git a/src/commandline_frame.ts b/src/commandline_frame.ts index c4d1e85e..2c58bedd 100644 --- a/src/commandline_frame.ts +++ b/src/commandline_frame.ts @@ -122,6 +122,7 @@ export function enableCompletions() { RssCompletionSource, SessionsCompletionSource, SettingsCompletionSource, + TabGroupCompletionSource, WindowCompletionSource, ExtensionsCompletionSource, ] @@ -392,6 +393,7 @@ Messaging.addListener("commandline_frame", Messaging.attributeCaller(SELF)) import { getCommandlineFns } from "@src/lib/commandline_cmds" import { KeyEventLike } from "./lib/keyseq" +import { TabGroupCompletionSource } from "./completions/TabGroup" commandline_state.fns = getCommandlineFns(commandline_state) Messaging.addListener( "commandline_cmd", diff --git a/src/completions/Tab.ts b/src/completions/Tab.ts index 3af2f6b4..db4f4c07 100644 --- a/src/completions/Tab.ts +++ b/src/completions/Tab.ts @@ -168,9 +168,19 @@ export class BufferCompletionSource extends Completions.CompletionSourceFuse { } private async fillOptions() { - const tabs: browser.tabs.Tab[] = await browserBg.tabs.query({ - currentWindow: true, - }) + let tabs: browser.tabs.Tab[] + + if (config.get("tabshowhidden") === "true") { + tabs = await browserBg.tabs.query({ + currentWindow: true + }) + } else { + tabs = await browserBg.tabs.query({ + currentWindow: true, + hidden: false + }) + } + const options = [] // Get alternative tab, defined as last accessed tab. tabs.sort((a, b) => b.lastAccessed - a.lastAccessed) diff --git a/src/completions/TabGroup.ts b/src/completions/TabGroup.ts new file mode 100644 index 00000000..5ac6cc2f --- /dev/null +++ b/src/completions/TabGroup.ts @@ -0,0 +1,70 @@ +import * as Completions from "@src/completions" +import { tgroups, windowTgroup, tgroupTabs } from "@src/lib/tab_groups" + +class TabGroupCompletionOption extends Completions.CompletionOptionHTML + implements Completions.CompletionOptionFuse { + public fuseKeys = [] + + constructor(group: string, tabCount: number) { + super() + this.value = group + this.fuseKeys.push(group) + this.html = html` + ${group} + ${tabCount} tab${ + tabCount !== 1 ? "s" : "" + } + ` + } +} + +export class TabGroupCompletionSource extends Completions.CompletionSourceFuse { + public options: TabGroupCompletionOption[] + + constructor(private _parent: any) { + super( + ["tgroupswitch", "tgroupmove"], + "TabGroupCompletionSource", + "Tab Groups", + ) + + this.updateOptions() + this._parent.appendChild(this.node) + } + + async onInput(_: string) {} + + async filter(exstr: string) { + this.lastExstr = exstr + const [prefix] = this.splitOnPrefix(exstr) + + // Hide self and stop if prefixes don't match + if (prefix) { + // Show self if prefix and currently hidden + if (this.state === "hidden") { + this.state = "normal" + } + } else { + this.state = "hidden" + return + } + + return this.updateDisplay() + } + + private async updateOptions() { + const currentGroup = await windowTgroup() + const otherGroups = [...(await tgroups())].filter( + group => group !== currentGroup, + ) + this.options = await Promise.all( + otherGroups.map(async group => { + const tabCount = (await tgroupTabs(group)).length + const o = new TabGroupCompletionOption(group, tabCount) + o.state = "normal" + return o + }), + ) + return this.updateDisplay() + } +} diff --git a/src/content.ts b/src/content.ts index f2419ff2..947e6aa0 100644 --- a/src/content.ts +++ b/src/content.ts @@ -155,6 +155,7 @@ import * as urlutils from "@src/lib/url_util" import * as scrolling from "@src/content/scrolling" import * as R from "ramda" import * as visual from "@src/lib/visual" +import { tabTgroup } from "./lib/tab_groups" /* tslint:disable:import-spacing */ ;(window as any).tri = Object.assign(Object.create(null), { browserBg: webext.browserBg, @@ -295,16 +296,18 @@ config.getAsync("modeindicator").then(mode => { }) } - addContentStateChangedListener((property, oldMode, oldValue, newValue) => { + addContentStateChangedListener(async (property, oldMode, oldValue, newValue) => { let mode = newValue + let group = "" let suffix = "" let result = "" if (property !== "mode") { if (property === "suffix") { mode = oldMode suffix = newValue - } else { - return + } else if (property === "group") { + mode = oldMode + group = newValue } } @@ -330,10 +333,17 @@ config.getAsync("modeindicator").then(mode => { } else { result = mode } + const modeindicatorshowkeys = Config.get("modeindicatorshowkeys") if (modeindicatorshowkeys === "true" && suffix !== "") { result = mode + " " + suffix } + + const tabGroup = await tabTgroup() + if (tabGroup) { + result = result + " | " + tabGroup + } + logger.debug( "statusindicator: ", result, diff --git a/src/content/state_content.ts b/src/content/state_content.ts index b2a78247..d5948c10 100644 --- a/src/content/state_content.ts +++ b/src/content/state_content.ts @@ -20,6 +20,8 @@ export class PrevInput { class ContentState { mode: ModeName = "normal" suffix = "" + // to trigger status indicator updates + group: string = "" } export type ContentStateProperty = @@ -27,6 +29,7 @@ export type ContentStateProperty = | "cmdHistory" | "prevInputs" | "suffix" + | "group" export type ContentStateChangedCallback = ( property: ContentStateProperty, diff --git a/src/excmds.ts b/src/excmds.ts index a8558236..d459ae72 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -164,6 +164,7 @@ import * as Updates from "@src/lib/updates" import * as Extensions from "@src/lib/extension_info" import * as webrequests from "@src/background/webrequests" import * as commandsHelper from "@src/background/commands" +import { tgroups, tgroupActivate, setTabTgroup, setWindowTgroup, setTgroups, windowTgroup, tgroupClearOldInfo, tgroupLastTabId, tgroupTabs, clearAllTgroupInfo, tgroupActivateLast, tgroupHandleTabActivated, tgroupHandleTabCreated, tgroupHandleTabAttached, tgroupHandleTabUpdated, tgroupHandleTabRemoved, tgroupHandleTabDetached } from "./lib/tab_groups" ALL_EXCMDS = { "": BGSELF, @@ -2803,7 +2804,208 @@ export async function recontain(containerName: string) { } // }}} -// + +// {{{ TAB GROUPS +/** @hidden */ +//#background_helper +// { +browser.tabs.onCreated.addListener(tgroupHandleTabCreated) +browser.tabs.onRemoved.addListener(tgroupHandleTabRemoved) +browser.tabs.onDetached.addListener(tgroupHandleTabDetached) +browser.tabs.onAttached.addListener(tgroupHandleTabAttached) +browser.tabs.onActivated.addListener(tgroupHandleTabActivated) +browser.tabs.onUpdated.addListener(tgroupHandleTabUpdated) +// } + +/** @hidden */ +//#content +export function setContentStateGroup(name: string) { + contentState.group = name +} + +/** + * Create a new tab group in the current window. + * + * Tab groups are a way of organizing different groups of related tabs within a + * single window. Groups allow you to have different named contexts and show + * only the tabs for a single group at a time. + * + * @param name The name of the tab group to create. + * + * If no tab groups exist, set the tab group name for all existing tabs in the + * window. Otherwise open a new tab and hide all tabs in the old tab group. + * + * Tab groups exist only for a single window. + * + */ +//#background +export async function tgroupcreate(name: string) { + const promises = [] + const groups = await tgroups() + + if (groups.has(name)) { + throw new Error(`Tab group "${name}" already exists`) + } + + if (groups.size > 0) { + await setWindowTgroup(name) + const initialUrl = await config.get("tabgroupnewtaburls")[name] + await tabopen(initialUrl) + promises.push(tgroupTabs(name, true).then(tabs => browserBg.tabs.hide(tabs.map(tab => tab.id)))) + } else { + promises.push(browser.tabs.query({currentWindow: true}).then((tabs) => { + setTabTgroup(name, tabs.map(({ id }) => id)) + // trigger status line update + setContentStateGroup(name) + })) + promises.push(setWindowTgroup(name)) + } + + groups.add(name) + promises.push(setTgroups(groups)) + return Promise.all(promises).then(() => name) +} + +/** + * Switch to a different tab group, hiding all other tabs. + * + * @param name The name of the tab group to switch to. + * + * If the tab group does not exist, act like tgroupcreate. + * + */ +//#background +export async function tgroupswitch(name: string) { + if (name == await windowTgroup()) { + throw new Error(`Already on tab group "${name}"`) + } + + const groups = await tgroups() + if (groups.size > 0) { + if (groups.has(name)) { + return tgroupActivate(name).then(() => name) + } else { + return tgroupcreate(name).then(() => name) + } + } else { + return tgroupcreate(name).then(() => name) + } +} + +/** + * Switch to the previously active tab group. + */ +//#background +export async function tgrouplast() { + if ((await tgroups()).size < 2) { + throw new Error("No last tab group") + } + + return tgroupActivateLast() +} + +/** + * Rename the current tab group. + * + * @param name The new name of the tab group. + * + */ +//#background +export async function tgrouprename(name: string) { + if ((await tgroups()).size == 0) { + throw new Error("No tab groups exist") + } + + return tgroupClearOldInfo(await windowTgroup(), name).then(() => { + // trigger status line update + setContentStateGroup(name) + return name + }) +} + +/** + * Close the current tab group. + * + * First switch to the previously active tab group. Do nothing if there is only + * one tab group. + * + */ +//#background +export async function tgroupclose() { + const groups = await tgroups() + if (groups.size == 0) { + throw new Error("No tab groups exist") + } else if (groups.size == 1) { + throw new Error("This is the only tab group") + } else if (groups.size > 1) { + const closeGroup = await windowTgroup() + const newTabGroup = await tgroupActivateLast() + await tgroupTabs(closeGroup).then(tabs => { + browser.tabs.remove(tabs.map(tab => tab.id)) + }) + return tgroupClearOldInfo(closeGroup).then(() => newTabGroup) + } +} + +/** + * Move the current tab to another tab group. + * + * @param name The name of the tab group to move the tab to. + * + * If this is the last tab in the tab group, also switch to tab group, keeping + * the current tab active. + * + */ +//#background +export async function tgroupmove(name: string) { + const groups = await tgroups() + const currentGroup = await windowTgroup() + + if (groups.size == 0) { + throw new Error("No tab groups exist") + } + if (!groups.has(name)) { + throw new Error(`Tab group "${name}" does not exist`) + } + if (name == currentGroup) { + throw new Error(`Tab is already on group "${name}"`) + } + + const tabCount = await tgroupTabs(currentGroup).then(tabs => tabs.length) + + await setTabTgroup(name) + const currentTabId = await activeTabId() + + // switch to other group if this is the last tab in the current group + if (tabCount == 1) { + return Promise.all([ + tgroupClearOldInfo(currentGroup, name), + tgroupTabs(name).then(tabs => { + browserBg.tabs.show(tabs.map(tab => tab.id)) + }), + ]).then(() => name) + } else { + const lastTabId = await tgroupLastTabId(currentGroup) + await tabSetActive(lastTabId) + return browser.tabs.hide(currentTabId).then(() => currentGroup) + } +} + +/** + * Delete all tab group information for the current window and show all tabs. + * + */ +//#background +export async function tgroupabort() { + if ((await tgroups()).size == 0) { + throw new Error("No tab groups exist") + } + + return clearAllTgroupInfo().then(() => undefined) +} + +// }}} + // {{{ MISC //#background diff --git a/src/lib/config.ts b/src/lib/config.ts index 80ec30e7..9783a3bc 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -543,6 +543,13 @@ export class default_config { o: "open", w: "winopen", t: "tabopen", + tabgroupabort: "tgroupabort", + tabgroupclose: "tgroupclose", + tabgroupcreate: "tgroupcreate", + tabgrouplast: "tgrouplast", + tabgroupmove: "tgroupmove", + tabgrouprename: "tgrouprename", + tabgroupswitch: "tgroupswitch", tabnew: "tabopen", tabm: "tabmove", tabo: "tabonly", @@ -971,6 +978,16 @@ export class default_config { */ auconcreatecontainer: "true" | "false" = "true" + /** + * Initial urls to navigate to when creating a new tab for a new tab group. + */ + tabgroupnewtaburls = {} + + /** + * Whether :tab shows completions for hidden tabs (e.g. tabs in other tab groups). + */ + tabshowhidden: "true" | "false" = "false" + /** * Number of most recent results to ask Firefox for. We display the top 20 or so most frequently visited ones. */ diff --git a/src/lib/tab_groups.ts b/src/lib/tab_groups.ts new file mode 100644 index 00000000..19bdb20a --- /dev/null +++ b/src/lib/tab_groups.ts @@ -0,0 +1,413 @@ +import { + activeTabId, + activeWindowId, + browserBg, + removeActiveWindowValue, +} from "./webext" + +/** + * Return a set of the current window's tab groups (empty if there are none). + * + */ +export async function tgroups() { + const groups = await browserBg.sessions.getWindowValue( + await activeWindowId(), + "tridactyl-tgroups", + ) + return new Set(groups as string[]) +} + +/** + * Set the current window's tab groups. + * + * @param groups The set of groups. + * + */ +export async function setTgroups(groups: Set) { + return browserBg.sessions.setWindowValue( + await activeWindowId(), + "tridactyl-tgroups", + [...groups], + ) +} + +/** + * Clear the current window's tab groups. + * + */ +export function clearTgroups() { + removeActiveWindowValue("tridactyl-tgroups") +} + +/** + * Return the active tab group for the a window or undefined. + * + * @param id The id of the window. Use the current window if not specified. + * + */ +export async function windowTgroup(id?: number) { + if (id === undefined) { + id = await activeWindowId() + } + return (browserBg.sessions.getWindowValue( + id, + "tridactyl-active-tgroup", + ) as unknown) as string +} + +/** + * Set the active tab group for a window. + * + * @param name The name of the new active tab group. + * @param id The id of the window. Use the current window if not specified. + * + */ +export async function setWindowTgroup(name: string, id?: number) { + if (id === undefined) { + id = await activeWindowId() + } + return browserBg.sessions.setWindowValue( + id, + "tridactyl-active-tgroup", + name, + ) +} + +/** + * Clear the active tab group for the current window. + * + */ +export function clearWindowTgroup() { + return removeActiveWindowValue("tridactyl-active-tgroup") +} + +/** + * Return a tab's tab group. + * + * @param id The id of the tab. Use the current tab if not specified. + * + */ +export async function tabTgroup(id?: number) { + if (id === undefined) { + id = await activeTabId() + } + return (browserBg.sessions.getTabValue( + id, + "tridactyl-tgroup", + ) as unknown) as string +} + +/** + * Return a list of tab ids. + * + * @param id An id, a list of ids, or undefined. + * + * If id is undefined, return a list of the current tab id. + * + */ +async function tabIdsOrCurrent(ids?: number | number[]): Promise { + if (!ids) { + ids = [await activeTabId()] + } else if (!Array.isArray(ids)) { + ids = [ids] + } + return ids +} + +/** + * Set the a tab's tab group. + * + * @param name The name of the tab group. + * @param id The id(s) of the tab(s). Use the current tab if not specified. + * + */ +export async function setTabTgroup(name: string, id?: number | number[]) { + const ids = await tabIdsOrCurrent(id) + return Promise.all( + ids.map(id => { + browserBg.sessions.setTabValue(id, "tridactyl-tgroup", name) + }), + ) +} + +/** + * Clear all the tab groups. + * + * @param id The id(s) of the tab(s). Use the current tab if not specified. + * + */ +export async function clearTabTgroup(id?: number | number[]) { + const ids = await tabIdsOrCurrent(id) + return Promise.all( + ids.map(id => { + browserBg.sessions.removeTabValue(id, "tridactyl-tgroup") + }), + ) +} + +/** + * Return a list of all tabs in a tab group. + * + * @param name The name of the tab group. + * @param other Whether to return the tabs not in the tab group instead. + * @param id The id of the window. Use the current window if not specified. + * + */ +export async function tgroupTabs( + name: string, + other: boolean = false, + id?: number, +): Promise { + if (id === undefined) { + id = await activeWindowId() + } + return browserBg.tabs.query({ windowId: id }).then(async tabs => { + const sameGroupIndices = await Promise.all( + tabs.map(async ({ id }) => { + const groupMatched = (await tabTgroup(id)) == name + return other ? !groupMatched : groupMatched + }), + ) + tabs = tabs.filter((_, index) => sameGroupIndices[index]) + return tabs + }) +} + +/** + * Return the id of the last selected tab in a tab group. + * + * @param name The name of the tab group. + * @param previous Whether to return the tab selected before the last tab. + * + */ +export async function tgroupLastTabId(name: string, previous: boolean = false) { + const tabs = await tgroupTabs(name) + tabs.sort((a, b) => b.lastAccessed - a.lastAccessed) + if (previous) { + return tabs[1].id + } else { + return tabs[0].id + } +} + +/** + * Clear stored information for a tab group. + * + * @param name The name of the tab group. + * @param newName A name to rename the group to. + * @param id The id of the window. Use the current window if not specified. + * + * If newName is specified, add it as a stored group (if it doesn't already + * exist), set the current window group to it, and set the group for all tabs in + * the old group to it. + * + */ +export async function tgroupClearOldInfo( + oldName: string, + newName?: string, + id?: number, +) { + const promises = [] + const groups = await tgroups() + groups.delete(oldName) + if (newName) { + groups.add(newName) + } + promises.push(setTgroups(groups)) + + if (id === undefined) { + id = await activeWindowId() + } + + if (newName) { + promises.push(setWindowTgroup(newName, id)) + promises.push( + tgroupTabs(oldName, false, id).then(tabs => { + setTabTgroup( + newName, + tabs.map(tab => tab.id), + ) + }), + ) + } + return Promise.all(promises) +} + +/** + * Activate the previously active tab in a tab group. + * + * @param name The name of the tab group to switch to. + * + */ +export async function tgroupActivate(name: string) { + const lastActiveTabId = await tgroupLastTabId(name) + // this will trigger tgroupHandleTabActivated + return browserBg.tabs.update(lastActiveTabId, { active: true }) +} + +/** + * Activate the last active tab in the last active tab group. + * + * Return the name of activated tab group. + * + */ +export async function tgroupActivateLast() { + const otherGroupsTabs = await tgroupTabs(await windowTgroup(), true) + otherGroupsTabs.sort((a, b) => b.lastAccessed - a.lastAccessed) + const lastTabId = otherGroupsTabs[0].id + return browserBg.tabs + .update(lastTabId, { active: true }) + .then(() => tabTgroup(lastTabId)) +} + +/** + * Clear all stored tab group information. + * + */ +export async function clearAllTgroupInfo() { + return Promise.all([ + clearTgroups(), + clearWindowTgroup(), + browser.tabs.query({ currentWindow: true }).then(async tabs => { + const ids = tabs.map(tab => tab.id) + await browser.tabs.show(ids) + return clearTabTgroup(ids) + }), + ]) +} + +/** + * Set the tab's tab group to its window's active tab group if there is one. + * + * Do nothing if the tab is already associated with a tab group. + * + * @param tab The tab that was just created. + * + */ +export async function tgroupHandleTabCreated(tab: browser.tabs.Tab) { + const windowGroup = await windowTgroup(tab.windowId) + + if (windowGroup) { + const tabGroup = await tabTgroup(tab.id) + if (!tabGroup) { + return setTabTgroup(windowGroup, tab.id) + } + } +} + +/** + * Set the tab's tab group to its window's active tab group if there is one. + * + * @param tabId The id of the tab that was just attached to this window. + * + */ +export async function tgroupHandleTabAttached(tabId: number, attachInfo) { + // NOTE this doesn't need to worry about a tab on another window previously + // being pinned because tabs become unpinned when you move them between + // windows + const windowGroup = await windowTgroup(attachInfo.newWindowId) + if (windowGroup) { + return setTabTgroup(windowGroup, tabId) + } +} + +/** + * Handle tab activation, possibly switching tab groups. + * + * If the new tab is from a different tab group, set it as the new tab group, + * show all its tabs, and hide all tabs from the old tab group. + */ +export async function tgroupHandleTabActivated(activeInfo) { + const windowGroup = await windowTgroup(activeInfo.windowId) + const tabGroup = await tabTgroup(activeInfo.tab) + const promises = [] + if (windowGroup && tabGroup && windowGroup != tabGroup) { + await setWindowTgroup(tabGroup, activeInfo.windowId) + + promises.push( + tgroupTabs(tabGroup, false, activeInfo.windowId).then(tabs => { + return browserBg.tabs.show(tabs.map(tab => tab.id)) + }), + ) + promises.push( + tgroupTabs(tabGroup, true, activeInfo.windowId).then(tabs => { + return browserBg.tabs.hide(tabs.map(tab => tab.id)) + }), + ) + } + return Promise.all(promises) +} + +/** + * Set or clear a tab's group if it was pinned or unpinned respectively. + * + * @param tabId The id of the tab that was just updated. + * + */ +export async function tgroupHandleTabUpdated( + tabId: number, + changeInfo, + tab: browser.tabs.Tab, +) { + if (changeInfo.pinned !== undefined) { + const windowGroup = await windowTgroup(tab.windowId) + if (windowGroup) { + if (changeInfo.pinned) { + return clearTabTgroup(tabId) + } else { + return setTabTgroup(windowGroup, tabId) + } + } + } +} + +/** + * Handle the last tab in a tab group being closed. + * + * Clear its information. + * + */ +export async function tgroupHandleTabRemoved(_tabId: number, removeInfo) { + if (!removeInfo.isWindowClosing) { + const windowGroup = await windowTgroup(removeInfo.windowId) + const tabCount = await tgroupTabs( + windowGroup, + false, + removeInfo.windowId, + ).then(tabs => tabs.length) + if (tabCount == 0) { + return tgroupClearOldInfo( + windowGroup, + undefined, + removeInfo.windowId, + ) + } + } +} + +/** + * Handle the last tab in a tab group being moved to another window. + * + * Clear its information. + * + */ +export async function tgroupHandleTabDetached(tabId: number, detachInfo) { + // clear tab's stored group; will automatically be changed if there are + // groups on the other window but otherwise it will still show up in the + // mode indicator + clearTabTgroup(tabId) + + const windowGroup = await windowTgroup(detachInfo.oldWindowId) + const tabCount = await tgroupTabs( + windowGroup, + false, + detachInfo.oldWindowId, + ).then(tabs => tabs.length) + if (tabCount == 0) { + return tgroupClearOldInfo( + windowGroup, + undefined, + detachInfo.oldWindowId, + ) + } +} diff --git a/src/lib/webext.ts b/src/lib/webext.ts index e16529f9..140ce169 100644 --- a/src/lib/webext.ts +++ b/src/lib/webext.ts @@ -61,6 +61,21 @@ export async function activeTabId() { return (await activeTab()).id } +/** + * Return the active window's id. + * + */ +export async function activeWindowId() { + return (await browserBg.windows.getCurrent()).id +} + +export async function removeActiveWindowValue(value) { + browserBg.sessions.removeWindowValue( + await activeWindowId(), + value, + ) +} + export async function activeTabContainerId() { return (await activeTab()).cookieStoreId } diff --git a/src/manifest.json b/src/manifest.json index 2fbea26b..5eebaec8 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -90,6 +90,7 @@ "search", "sessions", "storage", + "tabHide", "tabs", "topSites", "management",