Fix #2747: open/reopen container tabs in tab groups when appropriate

Firefox 137 introduced tab groups. Tab group web extension support is rolling out in Firefox 138 and later. Creating a new tab after the last tab in a tab group can inadvertently create the new tab outside of the tab group.

When a user opens a new tab that should be in a container, this patch will make sure that the new tab resides in the same tab group as the original tab.
This commit is contained in:
Stephen Thompson
2025-04-21 23:35:28 -04:00
parent a60f5bb1be
commit b6a1bff9e8
5 changed files with 122 additions and 26 deletions
+73 -12
View File
@@ -1,3 +1,11 @@
/**
* Firefox does not yet have the `tabGroups` API, which exposes this constant
* to indicate that a tab is not in a tab group. But the Firefox `tabs` API
* currently returns this constant value for `Tab.groupId`.
* @see https://searchfox.org/mozilla-central/rev/3b95c8dbe724b10390c96c1b9dd0f12c873e2f2e/browser/components/extensions/schemas/tabs.json#235
*/
const TAB_GROUP_ID_NONE = -1;
window.assignManager = {
MENU_ASSIGN_ID: "open-in-this-container",
MENU_REMOVE_ID: "remove-open-in-this-container",
@@ -326,7 +334,8 @@ window.assignManager = {
options.url,
tab.index + 1,
tab.active,
openTabId
openTabId,
tab.groupId
);
} else {
this.reloadPageInContainer(
@@ -336,7 +345,8 @@ window.assignManager = {
tab.index + 1,
tab.active,
siteSettings.neverAsk,
openTabId
openTabId,
tab.groupId
);
}
this.calculateContextMenu(tab);
@@ -727,7 +737,15 @@ window.assignManager = {
});
},
reloadPageInDefaultContainer(url, index, active, openerTabId) {
/**
* @param {string} url
* @param {number} index
* @param {boolean} active
* @param {number} [openerTabId]
* @param {number} [groupId]
* @returns {void}
*/
reloadPageInDefaultContainer(url, index, active, openerTabId, groupId) {
// To create a new tab in the default container, it is easiest just to omit the
// cookieStoreId entirely.
//
@@ -746,16 +764,58 @@ window.assignManager = {
// does not automatically return to the original opener tab. To get this desired behaviour,
// we MUST specify the openerTabId when creating the new tab.
const cookieStoreId = "firefox-default";
browser.tabs.create({url, cookieStoreId, index, active, openerTabId});
this.createTabWrapper(url, cookieStoreId, index, active, openerTabId, groupId);
},
reloadPageInContainer(url, currentUserContextId, userContextId, index, active, neverAsk = false, openerTabId = null) {
/**
* Wraps around `browser.tabs.create` and `browser.tabs.group` to create a
* tab and ensure that it ends up in the requested tab group, if applicable.
*
* @param {string} url
* @param {string} cookieStoreId
* @param {number} index
* @param {boolean} active
* @param {number} openerTabId
* @param {number} [groupId]
* @returns {Promise<Tab>}
*/
async createTabWrapper(url, cookieStoreId, index, active, openerTabId, groupId) {
const newTab = await browser.tabs.create({
url,
cookieStoreId,
index,
active,
openerTabId,
});
if (groupId && groupId !== TAB_GROUP_ID_NONE && browser.tabs.group) {
// If the original tab was in a tab group, make sure that the reopened tab
// stays in the same tab group.
await browser.tabs.group({ groupId, tabIds: newTab.id });
}
return newTab;
},
/**
* @param {string} url
* @param {string} currentUserContextId
* @param {string} userContextId
* @param {number} index
* @param {boolean} active
* @param {boolean} [neverAsk=false]
* @param {number} [openerTabId=null]
* @param {number} [groupId]
* @returns {Promise<Tab>}
*/
reloadPageInContainer(url, currentUserContextId, userContextId, index, active, neverAsk = false, openerTabId = null, groupId = undefined) {
const cookieStoreId = backgroundLogic.cookieStoreId(userContextId);
const loadPage = browser.runtime.getURL("confirm-page.html");
// False represents assignment is not permitted
// If the user has explicitly checked "Never Ask Again" on the warning page we will send them straight there
if (neverAsk) {
return browser.tabs.create({url, cookieStoreId, index, active, openerTabId});
return this.createTabWrapper(url, cookieStoreId, index, active, openerTabId, groupId);
} else {
let confirmUrl = `${loadPage}?url=${this.encodeURLProperty(url)}&cookieStoreId=${cookieStoreId}`;
let currentCookieStoreId;
@@ -763,13 +823,14 @@ window.assignManager = {
currentCookieStoreId = backgroundLogic.cookieStoreId(currentUserContextId);
confirmUrl += `&currentCookieStoreId=${currentCookieStoreId}`;
}
return browser.tabs.create({
url: confirmUrl,
cookieStoreId: currentCookieStoreId,
openerTabId,
return this.createTabWrapper(
confirmUrl,
currentCookieStoreId,
index,
active
}).then(() => {
active,
openerTabId,
groupId
).then(() => {
// We don't want to sync this URL ever nor clutter the users history
browser.history.deleteUrl({url: confirmUrl});
}).catch((e) => {