Remove tab counting code as also not needed, change tab counting to be per window. Prevent reopening of hidden tabs doesn't call itself. Fixes #750, Fixes #753, Fixes #756

This commit is contained in:
Jonathan Kingston 2017-08-24 14:57:36 +01:00
parent 17f2781e07
commit 0be03ebeb7
4 changed files with 100 additions and 167 deletions

View file

@ -52,6 +52,16 @@ const backgroundLogic = {
}, },
async openTab(options) { async openTab(options) {
const userContextId = ("userContextId" in options) ? options.userContextId : 0;
const cookieStoreId = backgroundLogic.cookieStoreId(userContextId);
// Unhide all hidden tabs
this.showTabs({
cookieStoreId
});
return this.openNewTab(options);
},
async openNewTab(options) {
let url = options.url || undefined; let url = options.url || undefined;
const userContextId = ("userContextId" in options) ? options.userContextId : 0; const userContextId = ("userContextId" in options) ? options.userContextId : 0;
const active = ("nofocus" in options) ? options.nofocus : true; const active = ("nofocus" in options) ? options.nofocus : true;
@ -64,10 +74,6 @@ const backgroundLogic = {
url = undefined; url = undefined;
} }
// Unhide all hidden tabs
this.showTabs({
cookieStoreId
});
return browser.tabs.create({ return browser.tabs.create({
url, url,
active, active,
@ -76,60 +82,61 @@ const backgroundLogic = {
}); });
}, },
async getTabs(options) { checkArgs(requiredArguments, options, methodName) {
if (!("cookieStoreId" in options)) { requiredArguments.forEach((argument) => {
return new Error("getTabs must be called with cookieStoreId argument."); if (!(argument in options)) {
} return new Error(`${methodName} must be called with ${argument} argument.`);
}
});
},
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(options.cookieStoreId); async getTabs(options) {
await identityState.remapTabsIfMissing(options.cookieStoreId); const requiredArguments = ["cookieStoreId", "windowId"];
const isKnownContainer = await identityState._isKnownContainer(userContextId); this.checkArgs(requiredArguments, options, "getTabs");
if (!isKnownContainer) { const { cookieStoreId, windowId } = options;
return [];
}
const list = []; const list = [];
const tabs = await this._containerTabs(options.cookieStoreId); const tabs = await browser.tabs.query({
cookieStoreId,
windowId
});
tabs.forEach((tab) => { tabs.forEach((tab) => {
list.push(identityState._createTabObject(tab)); list.push(identityState._createTabObject(tab));
}); });
const containerState = await identityState.storageArea.get(options.cookieStoreId); const containerState = await identityState.storageArea.get(cookieStoreId);
return list.concat(containerState.hiddenTabs); return list.concat(containerState.hiddenTabs);
}, },
async moveTabsToWindow(options) { async moveTabsToWindow(options) {
if (!("cookieStoreId" in options)) { const requiredArguments = ["cookieStoreId", "windowId"];
return new Error("moveTabsToWindow must be called with cookieStoreId argument."); this.checkArgs(requiredArguments, options, "moveTabsToWindow");
} const { cookieStoreId, windowId } = options;
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(options.cookieStoreId); const list = await browser.tabs.query({
await identityState.remapTabsIfMissing(options.cookieStoreId); cookieStoreId,
if (!identityState._isKnownContainer(userContextId)) { windowId
return null; });
}
const list = await identityState._matchTabsByContainer(options.cookieStoreId); const containerState = await identityState.storageArea.get(cookieStoreId);
const containerState = await identityState.storageArea.get(options.cookieStoreId);
// Nothing to do // Nothing to do
if (list.length === 0 && if (list.length === 0 &&
containerState.hiddenTabs.length === 0) { containerState.hiddenTabs.length === 0) {
return; return;
} }
const window = await browser.windows.create({ const newWindowObj = await browser.windows.create({
tabId: list.shift().id tabId: list.shift().id
}); });
browser.tabs.move(list.map((tab) => tab.id), { browser.tabs.move(list.map((tab) => tab.id), {
windowId: window.id, windowId: newWindowObj.id,
index: -1 index: -1
}); });
// Let's show the hidden tabs. // Let's show the hidden tabs.
for (let object of containerState.hiddenTabs) { // eslint-disable-line prefer-const for (let object of containerState.hiddenTabs) { // eslint-disable-line prefer-const
browser.tabs.create(object.url || DEFAULT_TAB, { browser.tabs.create(object.url || DEFAULT_TAB, {
windowId: window.id, windowId: newWindowObj.id,
cookieStoreId: options.cookieStoreId cookieStoreId
}); });
} }
@ -138,31 +145,46 @@ const backgroundLogic = {
// Let's close all the normal tab in the new window. In theory it // Let's close all the normal tab in the new window. In theory it
// should be only the first tab, but maybe there are addons doing // should be only the first tab, but maybe there are addons doing
// crazy stuff. // crazy stuff.
const tabs = browser.tabs.query({windowId: window.id}); const tabs = browser.tabs.query({windowId: newWindowObj.id});
for (let tab of tabs) { // eslint-disable-line prefer-const for (let tab of tabs) { // eslint-disable-line prefer-const
if (tabs.cookieStoreId !== options.cookieStoreId) { if (tabs.cookieStoreId !== cookieStoreId) {
browser.tabs.remove(tab.id); browser.tabs.remove(tab.id);
} }
} }
return await identityState.storageArea.set(options.cookieStoreId, containerState); return await identityState.storageArea.set(cookieStoreId, containerState);
}, },
async _closeTabs(userContextId) { async _closeTabs(userContextId, windowId = false) {
const cookieStoreId = this.cookieStoreId(userContextId); const cookieStoreId = this.cookieStoreId(userContextId);
const tabs = await this._containerTabs(cookieStoreId); let tabs;
/* if we have no windowId we are going to close all this container (used for deleting) */
if (windowId) {
tabs = await browser.tabs.query({
cookieStoreId,
windowId
});
} else {
await browser.tabs.query({
cookieStoreId
});
}
const tabIds = tabs.map((tab) => tab.id); const tabIds = tabs.map((tab) => tab.id);
return browser.tabs.remove(tabIds); return browser.tabs.remove(tabIds);
}, },
async queryIdentitiesState() { async queryIdentitiesState(windowId) {
const identities = await browser.contextualIdentities.query({}); const identities = await browser.contextualIdentities.query({});
const identitiesOutput = {}; const identitiesOutput = {};
const identitiesPromise = identities.map(async function (identity) { const identitiesPromise = identities.map(async function (identity) {
await identityState.remapTabsIfMissing(identity.cookieStoreId); const { cookieStoreId } = identity;
const containerState = await identityState.storageArea.get(identity.cookieStoreId); const containerState = await identityState.storageArea.get(cookieStoreId);
identitiesOutput[identity.cookieStoreId] = { const openTabs = await browser.tabs.query({
cookieStoreId,
windowId
});
identitiesOutput[cookieStoreId] = {
hasHiddenTabs: !!containerState.hiddenTabs.length, hasHiddenTabs: !!containerState.hiddenTabs.length,
hasOpenTabs: !!containerState.openTabs hasOpenTabs: !!openTabs.length
}; };
return; return;
}); });
@ -172,15 +194,15 @@ const backgroundLogic = {
async sortTabs() { async sortTabs() {
const windows = await browser.windows.getAll(); const windows = await browser.windows.getAll();
for (let window of windows) { // eslint-disable-line prefer-const for (let windowObj of windows) { // eslint-disable-line prefer-const
// First the pinned tabs, then the normal ones. // First the pinned tabs, then the normal ones.
await this._sortTabsInternal(window, true); await this._sortTabsInternal(windowObj, true);
await this._sortTabsInternal(window, false); await this._sortTabsInternal(windowObj, false);
} }
}, },
async _sortTabsInternal(window, pinnedTabs) { async _sortTabsInternal(windowObj, pinnedTabs) {
const tabs = await browser.tabs.query({windowId: window.id}); const tabs = await browser.tabs.query({windowId: windowObj.id});
let pos = 0; let pos = 0;
// Let's collect UCIs/tabs for this window. // Let's collect UCIs/tabs for this window.
@ -212,28 +234,22 @@ const backgroundLogic = {
for (const tab of tabs) { for (const tab of tabs) {
++pos; ++pos;
browser.tabs.move(tab.id, { browser.tabs.move(tab.id, {
windowId: window.id, windowId: windowObj.id,
index: pos index: pos
}); });
//xulWindow.gBrowser.moveTabTo(tab, pos++);
} }
}); });
}, },
async hideTabs(options) { async hideTabs(options) {
if (!("cookieStoreId" in options)) { const requiredArguments = ["cookieStoreId", "windowId"];
return new Error("hideTabs must be called with cookieStoreId option."); this.checkArgs(requiredArguments, options, "hideTabs");
} const { cookieStoreId, windowId } = options;
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(options.cookieStoreId); const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(cookieStoreId);
await identityState.remapTabsIfMissing(options.cookieStoreId);
const isKnownContainer = await identityState._isKnownContainer(userContextId);
if (!isKnownContainer) {
return null;
}
const containerState = await identityState.storeHidden(options.cookieStoreId); const containerState = await identityState.storeHidden(cookieStoreId, windowId);
await this._closeTabs(userContextId); await this._closeTabs(userContextId, windowId);
return containerState; return containerState;
}, },
@ -243,17 +259,12 @@ const backgroundLogic = {
} }
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(options.cookieStoreId); const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(options.cookieStoreId);
await identityState.remapTabsIfMissing(options.cookieStoreId);
if (!identityState._isKnownContainer(userContextId)) {
return null;
}
const promises = []; const promises = [];
const containerState = await identityState.storageArea.get(options.cookieStoreId); const containerState = await identityState.storageArea.get(options.cookieStoreId);
for (let object of containerState.hiddenTabs) { // eslint-disable-line prefer-const for (let object of containerState.hiddenTabs) { // eslint-disable-line prefer-const
promises.push(this.openTab({ promises.push(this.openNewTab({
userContextId: userContextId, userContextId: userContextId,
url: object.url, url: object.url,
nofocus: options.nofocus || false, nofocus: options.nofocus || false,
@ -269,12 +280,6 @@ const backgroundLogic = {
cookieStoreId(userContextId) { cookieStoreId(userContextId) {
return `firefox-container-${userContextId}`; return `firefox-container-${userContextId}`;
}, }
_containerTabs(cookieStoreId) {
return browser.tabs.query({
cookieStoreId
}).catch((e) => {throw e;});
},
}; };

View file

@ -13,7 +13,10 @@ const identityState = {
if (storageResponse && storeKey in storageResponse) { if (storageResponse && storeKey in storageResponse) {
return storageResponse[storeKey]; return storageResponse[storeKey];
} }
return null; const defaultContainerState = identityState._createIdentityState();
await this.set(cookieStoreId, defaultContainerState);
return defaultContainerState;
}, },
set(cookieStoreId, data) { set(cookieStoreId, data) {
@ -29,19 +32,13 @@ const identityState = {
} }
}, },
async _isKnownContainer(userContextId) {
const cookieStoreId = backgroundLogic.cookieStoreId(userContextId);
const state = await this.storageArea.get(cookieStoreId);
return !!state;
},
_createTabObject(tab) { _createTabObject(tab) {
return Object.assign({}, tab); return Object.assign({}, tab);
}, },
async storeHidden(cookieStoreId) { async storeHidden(cookieStoreId, windowId) {
const containerState = await this.storageArea.get(cookieStoreId); const containerState = await this.storageArea.get(cookieStoreId);
const tabsByContainer = await this._matchTabsByContainer(cookieStoreId); const tabsByContainer = await browser.tabs.query({cookieStoreId, windowId});
tabsByContainer.forEach((tab) => { tabsByContainer.forEach((tab) => {
const tabObject = this._createTabObject(tab); const tabObject = this._createTabObject(tab);
// This tab is going to be closed. Let's mark this tabObject as // This tab is going to be closed. Let's mark this tabObject as
@ -54,89 +51,9 @@ const identityState = {
return this.storageArea.set(cookieStoreId, containerState); return this.storageArea.set(cookieStoreId, containerState);
}, },
async containersCounts() {
let containersCounts = { // eslint-disable-line prefer-const
"shown": 0,
"hidden": 0,
"total": 0
};
const containers = await browser.contextualIdentities.query({});
for (const id in containers) {
const container = containers[id];
await this.remapTabsIfMissing(container.cookieStoreId);
const containerState = await this.storageArea.get(container.cookieStoreId);
if (containerState.openTabs > 0) {
++containersCounts.shown;
++containersCounts.total;
continue;
} else if (containerState.hiddenTabs.length > 0) {
++containersCounts.hidden;
++containersCounts.total;
continue;
}
}
return containersCounts;
},
async containerTabCount(cookieStoreId) {
// Returns the total of open and hidden tabs with this userContextId
let containerTabsCount = 0;
await identityState.remapTabsIfMissing(cookieStoreId);
const containerState = await this.storageArea.get(cookieStoreId);
containerTabsCount += containerState.openTabs;
containerTabsCount += containerState.hiddenTabs.length;
return containerTabsCount;
},
async totalContainerTabsCount() {
// Returns the number of total open tabs across ALL containers
let totalContainerTabsCount = 0;
const containers = await browser.contextualIdentities.query({});
for (const id in containers) {
const container = containers[id];
const cookieStoreId = container.cookieStoreId;
await identityState.remapTabsIfMissing(cookieStoreId);
totalContainerTabsCount += await this.storageArea.get(cookieStoreId).openTabs;
}
return totalContainerTabsCount;
},
async totalNonContainerTabsCount() {
// Returns the number of open tabs NOT IN a container
let totalNonContainerTabsCount = 0;
const tabs = await browser.tabs.query({});
for (const tab of tabs) {
const userContextId = backgroundLogic.getUserContextIdFromCookieStoreId(tab.cookieStoreId);
if (userContextId === 0) {
++totalNonContainerTabsCount;
}
}
return totalNonContainerTabsCount;
},
async remapTabsIfMissing(cookieStoreId) {
// We already know this cookieStoreId.
const containerState = await this.storageArea.get(cookieStoreId) || this._createIdentityState();
await this.storageArea.set(cookieStoreId, containerState);
await this.remapTabsFromUserContextId(cookieStoreId);
},
_matchTabsByContainer(cookieStoreId) {
return browser.tabs.query({cookieStoreId});
},
async remapTabsFromUserContextId(cookieStoreId) {
const tabsByContainer = await this._matchTabsByContainer(cookieStoreId);
const containerState = await this.storageArea.get(cookieStoreId);
containerState.openTabs = tabsByContainer.length;
await this.storageArea.set(cookieStoreId, containerState);
},
_createIdentityState() { _createIdentityState() {
return { return {
hiddenTabs: [], hiddenTabs: []
openTabs: 0
}; };
}, },
}; };

View file

@ -45,23 +45,28 @@ const messageHandler = {
backgroundLogic.showTabs({cookieStoreId: m.cookieStoreId}); backgroundLogic.showTabs({cookieStoreId: m.cookieStoreId});
break; break;
case "hideTabs": case "hideTabs":
backgroundLogic.hideTabs({cookieStoreId: m.cookieStoreId}); backgroundLogic.hideTabs({
cookieStoreId: m.cookieStoreId,
windowId: m.windowId
});
break; break;
case "checkIncompatibleAddons": case "checkIncompatibleAddons":
// TODO // TODO
break; break;
case "moveTabsToWindow": case "moveTabsToWindow":
response = backgroundLogic.moveTabsToWindow({ response = backgroundLogic.moveTabsToWindow({
cookieStoreId: m.cookieStoreId cookieStoreId: m.cookieStoreId,
windowId: m.windowId
}); });
break; break;
case "getTabs": case "getTabs":
response = backgroundLogic.getTabs({ response = backgroundLogic.getTabs({
cookieStoreId: m.cookieStoreId cookieStoreId: m.cookieStoreId,
windowId: m.windowId
}); });
break; break;
case "queryIdentitiesState": case "queryIdentitiesState":
response = backgroundLogic.queryIdentitiesState(); response = backgroundLogic.queryIdentitiesState(m.message.windowId);
break; break;
case "exemptContainerAssignment": case "exemptContainerAssignment":
response = assignManager._exemptTab(m); response = assignManager._exemptTab(m);

View file

@ -190,7 +190,10 @@ const Logic = {
const [identities, state] = await Promise.all([ const [identities, state] = await Promise.all([
browser.contextualIdentities.query({}), browser.contextualIdentities.query({}),
browser.runtime.sendMessage({ browser.runtime.sendMessage({
method: "queryIdentitiesState" method: "queryIdentitiesState",
message: {
windowId: browser.windows.WINDOW_ID_CURRENT
}
}) })
]); ]);
this._identities = identities.map((identity) => { this._identities = identities.map((identity) => {
@ -654,6 +657,7 @@ Logic.registerPanel(P_CONTAINER_INFO, {
try { try {
browser.runtime.sendMessage({ browser.runtime.sendMessage({
method: identity.hasHiddenTabs ? "showTabs" : "hideTabs", method: identity.hasHiddenTabs ? "showTabs" : "hideTabs",
windowId: browser.windows.WINDOW_ID_CURRENT,
cookieStoreId: Logic.currentCookieStoreId() cookieStoreId: Logic.currentCookieStoreId()
}); });
window.close(); window.close();
@ -685,6 +689,7 @@ Logic.registerPanel(P_CONTAINER_INFO, {
Logic.addEnterHandler(moveTabsEl, async function () { Logic.addEnterHandler(moveTabsEl, async function () {
await browser.runtime.sendMessage({ await browser.runtime.sendMessage({
method: "moveTabsToWindow", method: "moveTabsToWindow",
windowId: browser.windows.WINDOW_ID_CURRENT,
cookieStoreId: Logic.currentIdentity().cookieStoreId, cookieStoreId: Logic.currentIdentity().cookieStoreId,
}); });
window.close(); window.close();
@ -726,6 +731,7 @@ Logic.registerPanel(P_CONTAINER_INFO, {
// Let's retrieve the list of tabs. // Let's retrieve the list of tabs.
const tabs = await browser.runtime.sendMessage({ const tabs = await browser.runtime.sendMessage({
method: "getTabs", method: "getTabs",
windowId: browser.windows.WINDOW_ID_CURRENT,
cookieStoreId: Logic.currentIdentity().cookieStoreId cookieStoreId: Logic.currentIdentity().cookieStoreId
}); });
return this.buildInfoTable(tabs); return this.buildInfoTable(tabs);