Hmm, you might try both and see which is more accurate.
@jscher2000
You know what? Thanks to this bug #2 would be more accurate! Of course if we work around it, then yes both ways would be identical in outcomes.
Oh man, I love performance questions!
@juraj.masiar
So glad you do! But I think you’ve misunderstood the question. For starters, we want to compare multiple tabs.query({ windowId })
(#1) versus a single windows.getAll({ populate: true })
(#2).
And that’s just the initialisation of the two ways to do this task. #1 will continue to do tabs.query
for every tab-count-changing event, while #2 would do so only when a window is created.
I’ve come up with the 2 versions of the addon, to make the difference clear. I’m not sure how to compare overall performance, maybe you can help, @juraj.masiar?
Here’s background1.js
for #1:
init();
browser.windows.onCreated.addListener(onWindowCreated);
browser.tabs.onCreated.addListener(onTabCreated);
browser.tabs.onRemoved.addListener(onTabRemoved);
browser.tabs.onDetached.addListener(onTabDetached);
browser.tabs.onAttached.addListener(onTabAttached);
async function init() {
const windows = await browser.windows.getAll();
windows.forEach(onWindowCreated);
}
function onWindowCreated(windowObj) {
updateBadge(windowObj.id);
}
function onTabCreated(tab) {
updateBadge(tab.windowId);
}
function onTabRemoved(tabId, info) {
if (info.isWindowClosing) return;
updateBadge(info.windowId, tabId);
}
function onTabDetached(tabId, info) {
updateBadge(info.oldWindowId);
}
function onTabAttached(tabId, info) {
updateBadge(info.newWindowId);
}
// TODO: 'Debounce' updateBadge to prevent multiple redundant tabs.query and browserAction.setBadgeText calls at once.
async function updateBadge(windowId, removedTabId) {
const text = `${await getTabCount(windowId, removedTabId)}`;
browser.browserAction.setBadgeText({ windowId, text });
}
// tabs.onRemoved fires too early and the count is one too many. https://bugzilla.mozilla.org/show_bug.cgi?id=1396758
// So removedTabId is passed here from onTabRemoved() to signal the need to work around the bug.
async function getTabCount(windowId, removedTabId) {
const tabs = await browser.tabs.query({ windowId });
let count = tabs.length;
if (removedTabId && tabs.find(tab => tab.id == removedTabId)) count--;
return count;
}
Here’s background2.js
for #2:
let tabCounts = {};
init();
browser.windows.onCreated.addListener(onWindowCreated);
browser.windows.onRemoved.addListener(onWindowRemoved);
browser.tabs.onCreated.addListener(onTabCreated);
browser.tabs.onRemoved.addListener(onTabRemoved);
browser.tabs.onDetached.addListener(onTabDetached);
browser.tabs.onAttached.addListener(onTabAttached);
async function init() {
const windows = await browser.windows.getAll({ populate: true });
for (const windowObj of windows) {
const windowId = windowObj.id;
tabCounts[windowId] = windowObj.tabs.length;
updateBadge(windowId);
}
}
async function onWindowCreated(windowObj) {
const windowId = windowObj.id;
// Via listeners, windowObj will not be populated. So we get the tabs here.
tabCounts[windowId] = (await browser.tabs.query({ windowId })).length;
updateBadge(windowId);
}
function onWindowRemoved(windowId) {
delete tabCounts[windowId];
}
function onTabCreated(tab) {
const windowId = tab.windowId;
if (isWindowBeingCreated(windowId)) return;
tabCounts[windowId]++;
updateBadge(windowId);
}
function onTabRemoved(tabId, info) {
if (info.isWindowClosing) return;
const windowId = info.windowId;
tabCounts[windowId]--;
updateBadge(windowId);
}
function onTabDetached(tabId, info) {
const windowId = info.oldWindowId;
tabCounts[windowId]--;
updateBadge(windowId);
}
function onTabAttached(tabId, info) {
const windowId = info.newWindowId;
if (isWindowBeingCreated(windowId)) return;
tabCounts[windowId]++;
updateBadge(windowId);
}
function isWindowBeingCreated(windowId) {
return !(windowId in tabCounts);
}
async function updateBadge(windowId) {
const text = `${tabCounts[windowId]}`;
browser.browserAction.setBadgeText({ windowId, text });
}
Here’s the manifest.json
, swap "background1.js"
with "background2.js"
as needed:
{
"manifest_version": 2,
"name": "Tab Counter",
"version": "0.0.1",
"background": {
"scripts": ["background1.js"]
},
"browser_action": {}
}
Notice that in background2.js
, we account for multiple events triggering from one action (e.g. detaching a tab also creates a window) using isWindowBeingCreated
. This is necessary to produce correct outcomes.
I can’t quite figure out how to do the same in background1.js
– which means multiple redundant tabs.query
calls – but outcomes will still be correct. We should, if we want a fair performance comparison…
Edit: I guess we can probably use the same isWindowBeingCreated
technique instead of coming up with a ‘debouncer’… I’ll try it out tomorrow. Sorry it’s late… Need sleep~