Files
2026-07-13 12:34:04 +08:00

161 lines
5.5 KiB
JavaScript

/**
* background.js — Service Worker
*
* Keeps Tab Harbor pages in sync when tabs change.
* The toolbar badge is intentionally kept empty.
*/
const TAB_HARBOR_BG_DEBUG = false;
if (TAB_HARBOR_BG_DEBUG) console.log('[tab-harbor bg] Service worker loaded, registering event listeners...');
// ─── Auto-close duplicate new tabs ───────────────────────────────────────────
function getNewTabUrls() {
return new Set([
chrome.runtime.getURL('index.html'),
chrome.runtime.getURL('extension/index.html'),
]);
}
function isNewTabBlank(tab, newTabUrls) {
const knownNewTabUrls = newTabUrls instanceof Set
? newTabUrls
: new Set(Array.isArray(newTabUrls) ? newTabUrls : [newTabUrls]);
const url = tab?.url || '';
const pendingUrl = tab?.pendingUrl || '';
if (pendingUrl && !knownNewTabUrls.has(pendingUrl) && pendingUrl !== 'chrome://newtab/') {
return false;
}
return (
url === 'chrome://newtab/' ||
knownNewTabUrls.has(url) ||
pendingUrl === 'chrome://newtab/' ||
knownNewTabUrls.has(pendingUrl) ||
url === '' ||
(tab.status === 'loading' && !url)
);
}
async function closeDuplicateNewTabs() {
try {
const stored = await chrome.storage.local.get('themePreferences');
const prefs = stored.themePreferences || {};
if (prefs.closeDuplicateNewTabsEnabled !== true) return;
const newTabUrls = getNewTabUrls();
const allTabs = await chrome.tabs.query({});
const blankTabs = allTabs.filter(tab => isNewTabBlank(tab, newTabUrls));
if (blankTabs.length <= 1) return;
// Keep the active tab; if none is active, keep the one with the largest id (newest)
const activeTab = blankTabs.find(tab => tab.active);
const toKeep = activeTab || blankTabs.reduce((a, b) => (a.id > b.id ? a : b));
const toClose = blankTabs.filter(tab => tab.id !== toKeep.id).map(tab => tab.id);
if (toClose.length > 0) await chrome.tabs.remove(toClose);
} catch (err) {
console.warn('[tab-harbor bg] closeDuplicateNewTabs error:', err.message);
}
}
async function updateBadge() {
try {
await chrome.action.setBadgeText({ text: '' });
} catch {
chrome.action.setBadgeText({ text: '' });
}
}
function getTabHarborDashboardUrls() {
const extensionId = chrome.runtime.id;
return new Set([
`chrome-extension://${extensionId}/index.html`,
`chrome-extension://${extensionId}/extension/index.html`,
]);
}
// ─── Event listeners ──────────────────────────────────────────────────────────
// Notify Tab Harbor pages when tabs change so they can refresh
async function notifyTabHarborPages(eventMeta = {}) {
try {
// Find all Tab Harbor dashboard pages
const dashboardUrls = getTabHarborDashboardUrls();
// Query all tabs and filter manually for more reliable matching
const allTabs = await chrome.tabs.query({});
const dashboardTabs = allTabs.filter(tab => {
if (!tab.url) return false;
// Tab Harbor can appear as either:
// 1. chrome-extension://EXTENSION_ID/index.html (direct access)
// 2. chrome-extension://EXTENSION_ID/extension/index.html (root manifest entry)
// 3. chrome://newtab/ with title "Tab Harbor" (new tab override)
return (
dashboardUrls.has(tab.url) ||
(tab.url === 'chrome://newtab/' && tab.title === 'Tab Harbor')
);
});
if (dashboardTabs.length === 0) return;
for (const tab of dashboardTabs) {
try {
await chrome.tabs.sendMessage(tab.id, {
action: 'tabs-changed',
source: eventMeta.source || 'tabs.changed',
triggerTabId: eventMeta.triggerTabId ?? null,
});
} catch (err) {
// Tab might be closed or not ready, ignore
console.warn(`[tab-harbor bg] Failed to notify tab ${tab.id}:`, err.message);
}
}
} catch (err) {
console.warn('[tab-harbor bg] Error in notifyTabHarborPages:', err);
}
}
// Update badge when the extension is first installed
chrome.runtime.onInstalled.addListener(() => {
updateBadge();
});
// Update badge when Chrome starts up
chrome.runtime.onStartup.addListener(() => {
updateBadge();
});
// Update badge and notify Tab Harbor pages whenever a tab is opened
chrome.tabs.onCreated.addListener((tab) => {
updateBadge();
notifyTabHarborPages({ source: 'tabs.onCreated', triggerTabId: tab?.id });
closeDuplicateNewTabs();
});
// Update badge and notify Tab Harbor pages whenever a tab is closed
chrome.tabs.onRemoved.addListener((tabId) => {
updateBadge();
notifyTabHarborPages({ source: 'tabs.onRemoved', triggerTabId: tabId });
});
// Update badge and notify Tab Harbor pages when a tab's URL changes (e.g. navigating to/from chrome://)
chrome.tabs.onUpdated.addListener((tabId) => {
updateBadge();
notifyTabHarborPages({ source: 'tabs.onUpdated', triggerTabId: tabId });
});
// ─── Initial run ─────────────────────────────────────────────────────────────
// Run once immediately when the service worker first loads
updateBadge();
// ─── Test exports ────────────────────────────────────────────────────────────
globalThis.TabHarborBackground = {
getNewTabUrls,
isNewTabBlank,
closeDuplicateNewTabs,
};