From cb8210f0be9bd9857f6c65a745f019e4e7254338 Mon Sep 17 00:00:00 2001 From: web96lol Date: Tue, 14 Oct 2025 03:28:40 -0700 Subject: [PATCH] Document verification of background bundle --- .../BUNDLE_VERIFICATION.md | 28 + .../background.bundle.js | 835 ++++++++++++++++++ duplicate-tabs-closer-master/background.js | 95 -- duplicate-tabs-closer-master/badge.js | 56 -- duplicate-tabs-closer-master/build/list.txt | 14 +- duplicate-tabs-closer-master/manifest-f.json | 18 +- .../messageListener.js | 24 - duplicate-tabs-closer-master/options.js | 207 ----- duplicate-tabs-closer-master/package.json | 2 +- duplicate-tabs-closer-master/scriptImport.js | 10 +- duplicate-tabs-closer-master/tabsInfo.js | 88 -- duplicate-tabs-closer-master/urlUtils.js | 70 -- duplicate-tabs-closer-master/worker.js | 301 ------- 13 files changed, 879 insertions(+), 869 deletions(-) create mode 100644 duplicate-tabs-closer-master/BUNDLE_VERIFICATION.md create mode 100644 duplicate-tabs-closer-master/background.bundle.js delete mode 100644 duplicate-tabs-closer-master/background.js delete mode 100644 duplicate-tabs-closer-master/badge.js delete mode 100644 duplicate-tabs-closer-master/messageListener.js delete mode 100644 duplicate-tabs-closer-master/options.js delete mode 100644 duplicate-tabs-closer-master/tabsInfo.js delete mode 100644 duplicate-tabs-closer-master/urlUtils.js delete mode 100644 duplicate-tabs-closer-master/worker.js diff --git a/duplicate-tabs-closer-master/BUNDLE_VERIFICATION.md b/duplicate-tabs-closer-master/BUNDLE_VERIFICATION.md new file mode 100644 index 0000000..6b59965 --- /dev/null +++ b/duplicate-tabs-closer-master/BUNDLE_VERIFICATION.md @@ -0,0 +1,28 @@ +# Background Bundle Verification + +This document records the verification performed to ensure that `background.bundle.js` +provides the exact same behaviour as the previous individual background scripts. + +## Method + +The bundle was compared against a plain concatenation of the original source files +(`tabsInfo.js`, `options.js`, `urlUtils.js`, `badge.js`, `worker.js`, +`messageListener.js`, and `background.js`) from the previous commit. + +``` +( + git show HEAD^:duplicate-tabs-closer-master/tabsInfo.js + git show HEAD^:duplicate-tabs-closer-master/options.js + git show HEAD^:duplicate-tabs-closer-master/urlUtils.js + git show HEAD^:duplicate-tabs-closer-master/badge.js + git show HEAD^:duplicate-tabs-closer-master/worker.js + git show HEAD^:duplicate-tabs-closer-master/messageListener.js + git show HEAD^:duplicate-tabs-closer-master/background.js +) > /tmp/oldbundle.js + +diff -u /tmp/oldbundle.js duplicate-tabs-closer-master/background.bundle.js +``` + +The diff showed no differences other than the missing trailing newline at the end +of the bundle file, confirming that the bundle executes the same code in the same +order as before. diff --git a/duplicate-tabs-closer-master/background.bundle.js b/duplicate-tabs-closer-master/background.bundle.js new file mode 100644 index 0000000..24b2fba --- /dev/null +++ b/duplicate-tabs-closer-master/background.bundle.js @@ -0,0 +1,835 @@ +"use strict"; + +class TabsInfo { + + constructor() { + this.tabs = new Map(); + this.nbDuplicateTabs = new Map(); + this.initialize(); + } + + async initialize() { + const openedTabs = await getTabs({ windowType: "normal" }); + for (const openedTab of openedTabs) { + this.setOpenedTab(openedTab); + } + } + + setNewTab(tabId) { + const tab = { url: null, lastComplete: null, ignored: false }; + this.tabs.set(tabId, tab); + } + + setOpenedTab(openedTab) { + const tab = { url: openedTab.url, lastComplete: Date.now(), ignored: false }; + this.tabs.set(openedTab.id, tab); + } + + ignoreTab(tabId, state) { + const tab = this.tabs.get(tabId); + tab.ignored = state; + this.tabs.set(tabId, tab); + } + + isIgnoredTab(tabId) { + const tab = this.tabs.get(tabId); + return (!tab || tab.ignored) ? true : false; + } + + getLastComplete(tabId) { + const tab = this.tabs.get(tabId); + return tab.lastComplete; + } + + updateTab(openedTab) { + const tab = this.tabs.get(openedTab.id); + tab.url = openedTab.url; + tab.lastComplete = Date.now(); + this.tabs.set(openedTab.id, tab); + } + + resetTab(tabId) { + this.setNewTab(tabId); + } + + hasUrlChanged(openedTab) { + const tab = this.tabs.get(openedTab.id); + return tab.url !== openedTab.url; + } + + removeTab(tabId) { + this.tabs.delete(tabId); + } + + hasTab(tabId) { + return this.tabs.has(tabId); + } + + hasDuplicateTabs(windowId) { + // Even nothing set, return true so it will force the refresh and set the badge. + return this.nbDuplicateTabs.get(windowId) !== "0"; + } + + getNbDuplicateTabs(windowId) { + return this.nbDuplicateTabs.get(windowId) || "0"; + } + + setNbDuplicateTabs(windowId, nbDuplicateTabs) { + this.nbDuplicateTabs.set(windowId, nbDuplicateTabs.toString()); + } + + clearDuplicateTabsInfo(windowId) { + if (this.nbDuplicateTabs.has(windowId)) this.nbDuplicateTabs.delete(windowId); + } + +} + +// eslint-disable-next-line no-unused-vars +const tabsInfo = new TabsInfo();"use strict"; + +const defaultOptions = { + shrunkMode: { + value: false + }, + onDuplicateTabDetected: { + value: "N" + }, + onRemainingTab: { + value: "A" + }, + keepTabBasedOnAge: { + value: "O" + }, + keepTabWithHttps: { + value: true + }, + keepPinnedTab: { + value: true + }, + keepTabWithHistory: { + value: false + }, + scope: { + value: "C" + }, + ignoreHashPart: { + value: false + }, + ignoreSearchPart: { + value: false + }, + ignorePathPart: { + value: false + }, + ignore3w: { + value: false + }, + caseInsensitive: { + value: false + }, + compareWithTitle: { + value: false + }, + onDuplicateTabDetectedPinned: { + value: true + }, + tabPriorityPinned: { + value: true + }, + matchingRulesPinned: { + value: true + }, + scopePinned: { + value: true + }, + customizationPinned: { + value: true + }, + whiteList: { + value: "" + }, + blackList: { + value: "" + }, + badgeColorDuplicateTabs: { + value: "#f22121" + }, + badgeColorNoDuplicateTabs: { + value: "#1e90ff" + }, + showBadgeIfNoDuplicateTabs: { + value: true + }, + closePopup: { + value: false + }, + environment: { + value: "firefox" + } +}; + +const setupDefaultOptions = async () => { + const environment = await getEnvironment(); + const options = Object.assign({}, defaultOptions); + options.environment.value = environment; + return options; +}; + +const getEnvironment = async () => { + const info = await getPlatformInfo(); + const environment = (info.os === "android") ? "android" : (typeof InstallTrigger !== "undefined") ? "firefox" : "chrome"; + return environment; +}; + +const getNotInReferenceKeys = (referenceKeys, keys) => { + const setKeys = new Set(keys); + return Array.from(referenceKeys).filter(key => !setKeys.has(key)); +}; + +// eslint-disable-next-line no-unused-vars +const initializeOptions = async () => { + const options = await getStoredOptions(); + let storedOptions = options.storedOptions; + if (storedOptions.length === 0) { + const intialOptions = await setupDefaultOptions(); + storedOptions = await saveStoredOptions(intialOptions); + } else { + const storedKeys = Object.keys(storedOptions).sort(); + const defaultKeys = Object.keys(defaultOptions).sort(); + if (JSON.stringify(storedKeys) != JSON.stringify(defaultKeys)) { + const obsoleteKeys = getNotInReferenceKeys(storedKeys, defaultKeys); + obsoleteKeys.forEach(key => delete storedOptions[key]); + const missingKeys = getNotInReferenceKeys(defaultKeys, storedKeys); + // eslint-disable-next-line no-return-assign + missingKeys.forEach(key => storedOptions[key] = { value: defaultOptions[key].value }); + const environment = await getEnvironment(); + storedOptions.environment.value = environment; + storedOptions = await saveStoredOptions(storedOptions, true); + } + } + setOptions(storedOptions); + setEnvironment(storedOptions); +}; + +// eslint-disable-next-line no-unused-vars +const setStoredOption = async (name, value, refresh) => { + const options = await getStoredOptions(); + const storedOptions = options.storedOptions; + storedOptions[name].value = value; + saveStoredOptions(storedOptions); + setOptions(storedOptions); + if (refresh) refreshGlobalDuplicateTabsInfo(); + else if (name === "onDuplicateTabDetected") setBadgeIcon(); + else if (name === "showBadgeIfNoDuplicateTabs" || name === "badgeColorNoDuplicateTabs" || name === "badgeColorDuplicateTabs") updateBadgeStyle(); +}; + +const options = {}; + +const setOptions = (storedOptions) => { + options.autoCloseTab = storedOptions.onDuplicateTabDetected.value === "A"; + options.defaultTabBehavior = storedOptions.onRemainingTab.value === "B"; + options.activateKeptTab = storedOptions.onRemainingTab.value === "A"; + options.keepNewerTab = storedOptions.keepTabBasedOnAge.value === "N"; + options.keepReloadOlderTab = storedOptions.keepTabBasedOnAge.value === "R"; + options.keepTabWithHttps = storedOptions.keepTabWithHttps.value; + options.keepPinnedTab = storedOptions.keepPinnedTab.value; + options.ignoreHashPart = storedOptions.ignoreHashPart.value; + options.ignoreSearchPart = storedOptions.ignoreSearchPart.value; + options.ignorePathPart = storedOptions.ignorePathPart.value; + options.compareWithTitle = storedOptions.compareWithTitle.value; + options.ignore3w = storedOptions.ignore3w.value; + options.caseInsensitive = storedOptions.caseInsensitive.value; + options.searchInAllWindows = storedOptions.scope.value === "A" || storedOptions.scope.value === "CA"; + options.searchPerContainer = storedOptions.scope.value === "CC" || storedOptions.scope.value === "CA"; + options.whiteList = whiteListToPattern(storedOptions.whiteList.value); + options.badgeColorDuplicateTabs = storedOptions.badgeColorDuplicateTabs.value; + options.badgeColorNoDuplicateTabs = storedOptions.badgeColorNoDuplicateTabs.value; + options.showBadgeIfNoDuplicateTabs = storedOptions.showBadgeIfNoDuplicateTabs.value; +}; + +const environment = { + isAndroid: false, + isFirefox: false, + isChrome: false +}; + +const setEnvironment = (storedOptions) => { + if (storedOptions.environment.value === "android") { + environment.isAndroid = true; + environment.isFirefox = false; + } else if (storedOptions.environment.value === "firefox") { + environment.isAndroid = false; + environment.isFirefox = true; + environment.isChrome = false; + } + else if (storedOptions.environment.value === "chrome") { + environment.isAndroid = false; + environment.isFirefox = false; + environment.isChrome = true; + } +}; + +// eslint-disable-next-line no-unused-vars +const isPanelOptionOpen = () => { + return false; //override for now, until replacement API comes in + /* const popups = chrome.extension.getViews({ type: "popup" }); + if (popups.length) return true; + const tabs = chrome.extension.getViews({ type: "tab" }); + return tabs.length > 0; */ +}; + +const whiteListToPattern = (whiteList) => { + const whiteListPatterns = new Set(); + const whiteListLines = whiteList.split("\n").map(line => line.trim()); + whiteListLines.forEach(whiteListLine => { + const length = whiteListLine.length; + let pattern = "^"; + for (let index = 0; index < length; index += 1) { + const character = whiteListLine.charAt(index); + pattern = (character === "*") ? `${pattern}.*` : pattern + character; + } + whiteListPatterns.add(new RegExp(`${pattern}$`)); + }); + return Array.from(whiteListPatterns); +};"use strict"; + +// eslint-disable-next-line no-unused-vars +const isBlankURL = (url) => url === "about:blank"; + +// eslint-disable-next-line no-unused-vars +const isChromeURL = (url) => url.startsWith("chrome://") || url.startsWith("view-source:chrome-search"); + +const isBrowserURL = (url) => url.startsWith("about:") || url.startsWith("chrome://"); + +const isValidURL = (url) => { + const regex = /^(f|ht)tps?:\/\//i; + return regex.test(url); +}; + +// eslint-disable-next-line no-unused-vars +const isHttps = (url) => { + const regex = /^https:\/\//i; + return regex.test(url); +}; + +// eslint-disable-next-line no-unused-vars +const getMatchingURL = (url) => { + if (!isValidURL(url)) return url; + let matchingURL = url; + if (options.ignorePathPart) { + const uri = new URL(matchingURL); + matchingURL = uri.origin; + } + else if (options.ignoreSearchPart) { + matchingURL = matchingURL.split("?")[0]; + } + else if (options.ignoreHashPart) { + matchingURL = matchingURL.split("#")[0]; + } + if (options.keepTabWithHttps) { + matchingURL = matchingURL.replace(/^http:\/\//i, "https://"); + } + if (options.ignore3w) { + matchingURL = matchingURL.replace("://www.", "://"); + } + if (options.caseInsensitive) { + matchingURL = matchingURL.toLowerCase(); + } + matchingURL = matchingURL.replace(/\/$/, ""); + return matchingURL; +}; + +// eslint-disable-next-line no-unused-vars +const getMatchPatternURL = (url) => { + let urlPattern = null; + if (isValidURL(url)) { + const uri = new URL(url); + urlPattern = `*://${uri.hostname}`; + if (options.ignorePathPart) { + urlPattern += "/*"; + } + else { + urlPattern += uri.pathname; + if (uri.search || uri.hash) { + urlPattern += "*"; + } + } + } + else if (isBrowserURL(url)) { + urlPattern = `${url}*`; + } + + return urlPattern; +};"use strict"; + +// eslint-disable-next-line no-unused-vars +const setBadgeIcon = () => { + chrome.action.setIcon({ path: options.autoCloseTab ? "images/auto_close_16.png" : "images/manual_close_16.png" }); + if (environment.isFirefox) browser.action.setBadgeTextColor({ color: "white" }); +}; + +const setBadge = async (windowId, activeTabId) => { + let nbDuplicateTabs = tabsInfo.getNbDuplicateTabs(windowId); + if (nbDuplicateTabs === "0" && !options.showBadgeIfNoDuplicateTabs) nbDuplicateTabs = ""; + const backgroundColor = (nbDuplicateTabs !== "0") ? options.badgeColorDuplicateTabs : options.badgeColorNoDuplicateTabs; + if (environment.isFirefox) { + setWindowBadgeText(windowId, nbDuplicateTabs); + setWindowBadgeBackgroundColor(windowId, backgroundColor); + } + else { + // eslint-disable-next-line no-param-reassign + activeTabId = activeTabId || await getActiveTabId(windowId); + if (activeTabId) { + setTabBadgeText(activeTabId, nbDuplicateTabs); + setTabBadgeBackgroundColor(activeTabId, backgroundColor); + } + } +}; + +const getNbDuplicateTabs = (duplicateTabsGroups) => { + let nbDuplicateTabs = 0; + if (duplicateTabsGroups.size !== 0) { + duplicateTabsGroups.forEach(duplicateTabs => (nbDuplicateTabs += duplicateTabs.size - 1)); + } + return nbDuplicateTabs; +}; + +const updateBadgeValue = (nbDuplicateTabs, windowId) => { + tabsInfo.setNbDuplicateTabs(windowId, nbDuplicateTabs); + setBadge(windowId); +}; + +// eslint-disable-next-line no-unused-vars +const updateBadgesValue = async (duplicateTabsGroups, windowId) => { + const nbDuplicateTabs = getNbDuplicateTabs(duplicateTabsGroups); + if (options.searchInAllWindows) { + const windows = await getWindows(); + windows.forEach(window => updateBadgeValue(nbDuplicateTabs, window.id)); + } + else { + updateBadgeValue(nbDuplicateTabs, windowId); + } +}; + +// eslint-disable-next-line no-unused-vars +const updateBadgeStyle = async () => { + const windows = await getWindows(); + windows.forEach(window => setBadge(window.id)); +};"use strict"; + +const isUrlWhiteListed = (url) => { + const matches = options.whiteList.filter(pattern => pattern.test(url)); + return matches.length !== 0; +}; + +const matchTitle = (tab1, tab2) => { + if (options.compareWithTitle) { + if ((isTabComplete(tab1) && isTabComplete(tab2)) && (tab1.title === tab2.title)) { + return true; + } + } + return false; +}; + +const getHttpsTabId = (observedTab, observedTabUrl, openedTab) => { + if (options.keepTabWithHttps) { + const regex = /^https:\/\//i; + const match1 = regex.test(observedTabUrl); + const match2 = regex.test(openedTab.url); + if (match1) { + return match2 ? null : observedTab.id; + } else { + return match2 ? openedTab.id : null; + } + } + return null; +}; + +const getPinnedTabId = (tab1, tab2) => { + if (options.keepPinnedTab) { + if (tab1.pinned) { + return tab2.pinned ? null : tab1.id; + } else { + return tab2.pinned ? tab2.id : null; + } + } + return null; +}; + +const getLastUpdatedTabId = (observedTab, openedTab) => { + const observedTabLastUpdate = tabsInfo.getLastComplete(observedTab.id); + const openedTabLastUpdate = tabsInfo.getLastComplete(openedTab.id); + if (options.keepNewerTab) { + if (observedTabLastUpdate === null) return observedTab.id; + if (openedTabLastUpdate === null) return openedTab.id; + return (observedTabLastUpdate > openedTabLastUpdate) ? observedTab.id : openedTab.id; + } else { + if (observedTabLastUpdate === null) return openedTab.id; + if (openedTabLastUpdate === null) return observedTab.id; + return (observedTabLastUpdate < openedTabLastUpdate) ? observedTab.id : openedTab.id; + } +}; + +const getFocusedTab = (observedTab, openedTab, activeWindowId, retainedTabId) => { + if (retainedTabId === observedTab.id) { + return ((openedTab.windowId === activeWindowId) && (openedTab.active || (observedTab.windowId !== activeWindowId)) ? openedTab.id : observedTab.id); + } + else { + return ((observedTab.windowId === activeWindowId) && (observedTab.active || (openedTab.windowId !== activeWindowId)) ? observedTab.id : openedTab.id); + } +}; + + +const getCloseInfo = (details) => { + const observedTab = details.observedTab; + const observedTabUrl = details.observedTabUrl || observedTab.url; + const openedTab = details.openedTab; + const activeWindowId = details.activeWindowId; + let retainedTabId = getPinnedTabId(observedTab, openedTab); + if (!retainedTabId) { + retainedTabId = getHttpsTabId(observedTab, observedTabUrl, openedTab); + if (!retainedTabId) { + retainedTabId = getLastUpdatedTabId(observedTab, openedTab); + if (activeWindowId) { + retainedTabId = getFocusedTab(observedTab, openedTab, activeWindowId, retainedTabId); + } + } + } + if (retainedTabId == observedTab.id) { + const keepInfo = { + observedTabClosed: false, + active: openedTab.active, + tabIndex: openedTab.index, + tabId: observedTab.id, + windowId: observedTab.windowId, + reloadTab: false + }; + return [openedTab.id, keepInfo]; + } else { + const keepInfo = { + observedTabClosed: true, + active: observedTab.active, + tabIndex: observedTab.index, + tabId: openedTab.id, + windowId: openedTab.windowId, + reloadTab: options.keepReloadOlderTab ? true : false + }; + return [observedTab.id, keepInfo]; + } +}; + +// eslint-disable-next-line no-unused-vars +const searchForDuplicateTabsToClose = async (observedTab, queryComplete, loadingUrl) => { + const observedTabUrl = loadingUrl || observedTab.url; + const observedWindowsId = observedTab.windowId; + if (isUrlWhiteListed(observedTabUrl)) { + if (isTabComplete(observedTab)) refreshDuplicateTabsInfo(observedWindowsId); + return; + } + const queryInfo = {}; + queryInfo.status = queryComplete ? "complete" : null; + queryInfo.url = getMatchPatternURL(observedTabUrl); + queryInfo.windowId = options.searchInAllWindows ? null : observedWindowsId; + if (environment.isFirefox) queryInfo.cookieStoreId = options.searchPerContainer ? observedTab.cookieStoreId : null; + const openedTabs = await getTabs(queryInfo); + if (openedTabs.length > 1) { + const matchingObservedTabUrl = getMatchingURL(observedTabUrl); + let match = false; + for (const openedTab of openedTabs) { + if ((openedTab.id === observedTab.id) || tabsInfo.isIgnoredTab(openedTab.id) || (isBlankURL(openedTab.url) && !isTabComplete(openedTab))) continue; + if ((getMatchingURL(openedTab.url) === matchingObservedTabUrl) || matchTitle(openedTab, observedTab)) { + match = true; + const [tabToCloseId, remainingTabInfo] = getCloseInfo({ observedTab: observedTab, observedTabUrl: observedTabUrl, openedTab: openedTab }); + closeDuplicateTab(tabToCloseId, remainingTabInfo); + if (remainingTabInfo.observedTabClosed) break; + } + } + if (!match) { + if (tabsInfo.hasDuplicateTabs(observedWindowsId)) refreshDuplicateTabsInfo(observedWindowsId); + else if (environment.isChrome && observedTab.active) setBadge(observedTab.windowId, observedTab.id); + } + } +}; + +const closeDuplicateTab = async (tabToCloseId, remainingTabInfo) => { + try { + tabsInfo.ignoreTab(tabToCloseId, true); + await removeTab(tabToCloseId); + } + catch (ex) { + tabsInfo.ignoreTab(tabToCloseId, false); + return; + } + if (tabsInfo.hasTab(tabToCloseId)) { + await wait(10); + if (tabsInfo.hasTab(tabToCloseId)) { + tabsInfo.ignoreTab(tabToCloseId, false); + refreshDuplicateTabsInfo(remainingTabInfo.windowId); + return; + } + } + handleRemainingTab(remainingTabInfo.windowId, remainingTabInfo); +}; + +const _handleRemainingTab = async (details) => { + if (!tabsInfo.hasTab(details.tabId)) return; + if (options.defaultTabBehavior && details.observedTabClosed) { + if (details.tabIndex > 0) moveTab(details.tabId, { index: details.tabIndex }); + if (details.active) activateTab(details.tabId); + } else if (options.activateKeptTab) { + focusTab(details.tabId, details.windowId); + } + if (details.reloadTab) { + tabsInfo.ignoreTab(details.tabId, true); + await reloadTab(details.tabId); + tabsInfo.ignoreTab(details.tabId, false); + } +}; + +const handleRemainingTab = debounce(_handleRemainingTab, 500); + +const handleObservedTab = (details) => { + const observedTab = details.tab; + const retainedTabs = details.retainedTabs; + const duplicateTabsGroups = details.duplicateTabsGroups; + let matchingTabURL = getMatchingURL(observedTab.url); + let matchingTabTitle = options.compareWithTitle && isTabComplete(observedTab) ? `title=${observedTab.title}` : null; + if (options.searchPerContainer) { + matchingTabURL += observedTab.cookieStoreId; + if (matchingTabTitle) matchingTabTitle += observedTab.cookieStoreId; + } + let matchingKey = matchingTabURL; + let retainedTab = retainedTabs.get(matchingKey); + if (!retainedTab) { + if (isTabComplete(observedTab)) retainedTabs.set(matchingKey, observedTab); + if (matchingTabTitle) { + matchingKey = matchingTabTitle; + retainedTab = retainedTabs.get(matchingKey); + if (!retainedTab) { + retainedTabs.set(matchingKey, observedTab); + } + } + } + if (retainedTab) { + if (details.closeTab) { + const [tabToCloseId] = getCloseInfo({ observedTab: observedTab, openedTab: retainedTab, activeWindowId: details.activeWindowId }); + if (tabToCloseId === observedTab.id) { + chrome.tabs.remove(observedTab.id); + } + else { + chrome.tabs.remove(retainedTab.id); + retainedTabs.set(matchingKey, observedTab); + } + } else { + const tabs = duplicateTabsGroups.get(matchingKey) || new Set([retainedTab]); + tabs.add(observedTab); + duplicateTabsGroups.set(matchingKey, tabs); + } + } +}; + +// eslint-disable-next-line no-unused-vars +const searchForDuplicateTabs = async (windowId, closeTabs) => { + const queryInfo = { windowType: "normal" }; + if (!options.searchInAllWindows) queryInfo.windowId = windowId; + const [activeWindowId, openedTabs] = await Promise.all([getActiveWindowId(), getTabs(queryInfo)]); + const duplicateTabsGroups = new Map(); + const retainedTabs = new Map(); + for (const openedTab of openedTabs) { + if ((isBlankURL(openedTab.url) && !isTabComplete(openedTab)) || tabsInfo.isIgnoredTab(openedTab.id)) continue; + const details = { + tab: openedTab, + retainedTabs: retainedTabs, + activeWindowId: activeWindowId, + closeTab: closeTabs, + duplicateTabsGroups: duplicateTabsGroups + }; + handleObservedTab(details); + } + if (!closeTabs) { + return { + duplicateTabsGroups: duplicateTabsGroups, + activeWindowId: activeWindowId + }; + } +}; + +// eslint-disable-next-line no-unused-vars +const closeDuplicateTabs = (windowId) => searchForDuplicateTabs(windowId, true); + +const setDuplicateTabPanel = async (duplicateTab, duplicateTabs) => { + let containerColor = ""; + if (environment.isFirefox && (!duplicateTab.incognito && duplicateTab.cookieStoreId !== "firefox-default")) { + const getContext = await browser.contextualIdentities.get(duplicateTab.cookieStoreId); + if (getContext) containerColor = getContext.color; + } + duplicateTabs.add({ + id: duplicateTab.id, + url: duplicateTab.url, + title: duplicateTab.title || duplicateTab.url, + windowId: duplicateTab.windowId, + containerColor: containerColor, + icon: duplicateTab.favIconUrl || "../images/default-favicon.png" + }); +}; + +const getDuplicateTabsForPanel = async (duplicateTabsGroups) => { + if (duplicateTabsGroups.size === 0) return null; + const duplicateTabsPanel = new Set(); + for (const tabsGroup of duplicateTabsGroups) { + const duplicateTabs = tabsGroup[1]; + await Promise.all(Array.from(duplicateTabs, duplicateTab => setDuplicateTabPanel(duplicateTab, duplicateTabsPanel))); + } + return Array.from(duplicateTabsPanel); +}; + +// eslint-disable-next-line no-unused-vars +const requestDuplicateTabsFromPanel = async (windowId) => { + const searchResult = await searchForDuplicateTabs(windowId, false); + sendDuplicateTabs(searchResult.duplicateTabsGroups); +}; + +const sendDuplicateTabs = async (duplicateTabsGroups) => { + const duplicateTabs = await getDuplicateTabsForPanel(duplicateTabsGroups); + chrome.runtime.sendMessage({ + action: "updateDuplicateTabsTable", + data: { "duplicateTabs": duplicateTabs } + }); +}; + +const _refreshDuplicateTabsInfo = async (windowId) => { + const searchResult = await searchForDuplicateTabs(windowId, false); + updateBadgesValue(searchResult.duplicateTabsGroups, windowId); + if (isPanelOptionOpen() && (options.searchInAllWindows || (windowId === searchResult.activeWindowId))) { + sendDuplicateTabs(searchResult.duplicateTabsGroups); + } +}; + +const refreshDuplicateTabsInfo = debounce(_refreshDuplicateTabsInfo, 300); + +// eslint-disable-next-line no-unused-vars +const refreshGlobalDuplicateTabsInfo = async () => { + if (options.searchInAllWindows) { + refreshDuplicateTabsInfo(); + } else { + const windows = await getWindows(); + windows.forEach(window => refreshDuplicateTabsInfo(window.id)); + } +};"use strict"; + +const handleMessage = (message, sender, response) => { + switch (message.action) { + case "setStoredOption": { + setStoredOption(message.data.name, message.data.value, message.data.refresh); + break; + } + case "getStoredOptions": { + getStoredOptions().then(storedOptions => response({ data: storedOptions })); + return true; + } + case "getDuplicateTabs": { + requestDuplicateTabsFromPanel(message.data.windowId); + break; + } + case "closeDuplicateTabs": { + closeDuplicateTabs(message.data.windowId); + break; + } + } +}; + +chrome.runtime.onMessage.addListener(handleMessage);"use strict"; + +const onCreatedTab = (tab) => { + tabsInfo.setNewTab(tab.id); + if (tab.status === "complete" && !isBlankURL(tab.url)) { + options.autoCloseTab ? searchForDuplicateTabsToClose(tab, true) : refreshDuplicateTabsInfo(tab.windowId); + } +}; + +const onBeforeNavigate = async (details) => { + if (options.autoCloseTab && (details.frameId == 0) && (details.tabId !== -1) && !isBlankURL(details.url)) { + if (tabsInfo.isIgnoredTab(details.tabId)) return; + const tab = await getTab(details.tabId); + if (tab) { + tabsInfo.resetTab(tab.id); + searchForDuplicateTabsToClose(tab, true, details.url); + } + } +}; + +const onCompletedTab = async (details) => { + if ((details.frameId == 0) && (details.tabId !== -1)) { + if (tabsInfo.isIgnoredTab(details.tabId)) return; + const tab = await getTab(details.tabId); + if (tab) { + tabsInfo.updateTab(tab); + options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); + } + } +}; + +const onUpdatedTab = (tabId, changeInfo, tab) => { + if (tabsInfo.isIgnoredTab(tabId)) return; + if (Object.prototype.hasOwnProperty.call(changeInfo, "status") && changeInfo.status === "complete") { + if (Object.prototype.hasOwnProperty.call(changeInfo, "url") && (changeInfo.url !== tab.url)) { + if (isBlankURL(tab.url) || !tab.favIconUrl || !tabsInfo.hasUrlChanged(tab)) return; + tabsInfo.updateTab(tab); + options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); + } + else if (isChromeURL(tab.url)) { + tabsInfo.updateTab(tab); + options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); + } + } +}; + +const onAttached = async (tabId) => { + const tab = await getTab(tabId); + if (tab) { + options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); + } +}; + +const onRemovedTab = (removedTabId, removeInfo) => { + tabsInfo.removeTab(removedTabId); + if (removeInfo.isWindowClosing) { + if (options.searchInAllWindows && tabsInfo.hasDuplicateTabs(removeInfo.windowId)) refreshDuplicateTabsInfo(); + tabsInfo.clearDuplicateTabsInfo(removeInfo.windowId); + } + else if (tabsInfo.hasDuplicateTabs(removeInfo.windowId)) { + refreshDuplicateTabsInfo(removeInfo.windowId); + } +}; + +const onDetachedTab = (detachedTabId, detachInfo) => { + if (tabsInfo.hasDuplicateTabs(detachInfo.oldWindowId)) refreshDuplicateTabsInfo(detachInfo.oldWindowId); +}; + +const onActivatedTab = (activeInfo) => { + // for Chrome only + if (tabsInfo.isIgnoredTab(activeInfo.tabId)) return; + setBadge(activeInfo.windowId, activeInfo.tabId); +}; + +const onCommand = (command) => { + if (command == "close-duplicate-tabs") closeDuplicateTabs(); +}; + +const start = async () => { + // eslint-disable-next-line no-unused-vars + await initializeOptions(); + setBadgeIcon(); + await refreshGlobalDuplicateTabsInfo(); + chrome.tabs.onCreated.addListener(onCreatedTab); + chrome.webNavigation.onBeforeNavigate.addListener(onBeforeNavigate); + chrome.tabs.onAttached.addListener(onAttached); + chrome.tabs.onDetached.addListener(onDetachedTab); + chrome.tabs.onUpdated.addListener(onUpdatedTab); + chrome.webNavigation.onCompleted.addListener(onCompletedTab); + chrome.tabs.onRemoved.addListener(onRemovedTab); + if (!environment.isFirefox) chrome.tabs.onActivated.addListener(onActivatedTab); + chrome.commands.onCommand.addListener(onCommand); +}; + +start(); diff --git a/duplicate-tabs-closer-master/background.js b/duplicate-tabs-closer-master/background.js deleted file mode 100644 index 4901df1..0000000 --- a/duplicate-tabs-closer-master/background.js +++ /dev/null @@ -1,95 +0,0 @@ -"use strict"; - -const onCreatedTab = (tab) => { - tabsInfo.setNewTab(tab.id); - if (tab.status === "complete" && !isBlankURL(tab.url)) { - options.autoCloseTab ? searchForDuplicateTabsToClose(tab, true) : refreshDuplicateTabsInfo(tab.windowId); - } -}; - -const onBeforeNavigate = async (details) => { - if (options.autoCloseTab && (details.frameId == 0) && (details.tabId !== -1) && !isBlankURL(details.url)) { - if (tabsInfo.isIgnoredTab(details.tabId)) return; - const tab = await getTab(details.tabId); - if (tab) { - tabsInfo.resetTab(tab.id); - searchForDuplicateTabsToClose(tab, true, details.url); - } - } -}; - -const onCompletedTab = async (details) => { - if ((details.frameId == 0) && (details.tabId !== -1)) { - if (tabsInfo.isIgnoredTab(details.tabId)) return; - const tab = await getTab(details.tabId); - if (tab) { - tabsInfo.updateTab(tab); - options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); - } - } -}; - -const onUpdatedTab = (tabId, changeInfo, tab) => { - if (tabsInfo.isIgnoredTab(tabId)) return; - if (Object.prototype.hasOwnProperty.call(changeInfo, "status") && changeInfo.status === "complete") { - if (Object.prototype.hasOwnProperty.call(changeInfo, "url") && (changeInfo.url !== tab.url)) { - if (isBlankURL(tab.url) || !tab.favIconUrl || !tabsInfo.hasUrlChanged(tab)) return; - tabsInfo.updateTab(tab); - options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); - } - else if (isChromeURL(tab.url)) { - tabsInfo.updateTab(tab); - options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); - } - } -}; - -const onAttached = async (tabId) => { - const tab = await getTab(tabId); - if (tab) { - options.autoCloseTab ? searchForDuplicateTabsToClose(tab) : refreshDuplicateTabsInfo(tab.windowId); - } -}; - -const onRemovedTab = (removedTabId, removeInfo) => { - tabsInfo.removeTab(removedTabId); - if (removeInfo.isWindowClosing) { - if (options.searchInAllWindows && tabsInfo.hasDuplicateTabs(removeInfo.windowId)) refreshDuplicateTabsInfo(); - tabsInfo.clearDuplicateTabsInfo(removeInfo.windowId); - } - else if (tabsInfo.hasDuplicateTabs(removeInfo.windowId)) { - refreshDuplicateTabsInfo(removeInfo.windowId); - } -}; - -const onDetachedTab = (detachedTabId, detachInfo) => { - if (tabsInfo.hasDuplicateTabs(detachInfo.oldWindowId)) refreshDuplicateTabsInfo(detachInfo.oldWindowId); -}; - -const onActivatedTab = (activeInfo) => { - // for Chrome only - if (tabsInfo.isIgnoredTab(activeInfo.tabId)) return; - setBadge(activeInfo.windowId, activeInfo.tabId); -}; - -const onCommand = (command) => { - if (command == "close-duplicate-tabs") closeDuplicateTabs(); -}; - -const start = async () => { - // eslint-disable-next-line no-unused-vars - await initializeOptions(); - setBadgeIcon(); - await refreshGlobalDuplicateTabsInfo(); - chrome.tabs.onCreated.addListener(onCreatedTab); - chrome.webNavigation.onBeforeNavigate.addListener(onBeforeNavigate); - chrome.tabs.onAttached.addListener(onAttached); - chrome.tabs.onDetached.addListener(onDetachedTab); - chrome.tabs.onUpdated.addListener(onUpdatedTab); - chrome.webNavigation.onCompleted.addListener(onCompletedTab); - chrome.tabs.onRemoved.addListener(onRemovedTab); - if (!environment.isFirefox) chrome.tabs.onActivated.addListener(onActivatedTab); - chrome.commands.onCommand.addListener(onCommand); -}; - -start(); \ No newline at end of file diff --git a/duplicate-tabs-closer-master/badge.js b/duplicate-tabs-closer-master/badge.js deleted file mode 100644 index 8b226eb..0000000 --- a/duplicate-tabs-closer-master/badge.js +++ /dev/null @@ -1,56 +0,0 @@ -"use strict"; - -// eslint-disable-next-line no-unused-vars -const setBadgeIcon = () => { - chrome.action.setIcon({ path: options.autoCloseTab ? "images/auto_close_16.png" : "images/manual_close_16.png" }); - if (environment.isFirefox) browser.action.setBadgeTextColor({ color: "white" }); -}; - -const setBadge = async (windowId, activeTabId) => { - let nbDuplicateTabs = tabsInfo.getNbDuplicateTabs(windowId); - if (nbDuplicateTabs === "0" && !options.showBadgeIfNoDuplicateTabs) nbDuplicateTabs = ""; - const backgroundColor = (nbDuplicateTabs !== "0") ? options.badgeColorDuplicateTabs : options.badgeColorNoDuplicateTabs; - if (environment.isFirefox) { - setWindowBadgeText(windowId, nbDuplicateTabs); - setWindowBadgeBackgroundColor(windowId, backgroundColor); - } - else { - // eslint-disable-next-line no-param-reassign - activeTabId = activeTabId || await getActiveTabId(windowId); - if (activeTabId) { - setTabBadgeText(activeTabId, nbDuplicateTabs); - setTabBadgeBackgroundColor(activeTabId, backgroundColor); - } - } -}; - -const getNbDuplicateTabs = (duplicateTabsGroups) => { - let nbDuplicateTabs = 0; - if (duplicateTabsGroups.size !== 0) { - duplicateTabsGroups.forEach(duplicateTabs => (nbDuplicateTabs += duplicateTabs.size - 1)); - } - return nbDuplicateTabs; -}; - -const updateBadgeValue = (nbDuplicateTabs, windowId) => { - tabsInfo.setNbDuplicateTabs(windowId, nbDuplicateTabs); - setBadge(windowId); -}; - -// eslint-disable-next-line no-unused-vars -const updateBadgesValue = async (duplicateTabsGroups, windowId) => { - const nbDuplicateTabs = getNbDuplicateTabs(duplicateTabsGroups); - if (options.searchInAllWindows) { - const windows = await getWindows(); - windows.forEach(window => updateBadgeValue(nbDuplicateTabs, window.id)); - } - else { - updateBadgeValue(nbDuplicateTabs, windowId); - } -}; - -// eslint-disable-next-line no-unused-vars -const updateBadgeStyle = async () => { - const windows = await getWindows(); - windows.forEach(window => setBadge(window.id)); -}; \ No newline at end of file diff --git a/duplicate-tabs-closer-master/build/list.txt b/duplicate-tabs-closer-master/build/list.txt index 9b37027..7283380 100644 --- a/duplicate-tabs-closer-master/build/list.txt +++ b/duplicate-tabs-closer-master/build/list.txt @@ -1,15 +1,9 @@ -background.js -badge.js -helper.js +background.bundle.js +helper.js LICENSE manifest.json -messageListener.js -options.js -README.md -scriptImport.js -tabsInfo.js -urlUtils.js -worker.js +README.md +scriptImport.js _locales ext_lib images diff --git a/duplicate-tabs-closer-master/manifest-f.json b/duplicate-tabs-closer-master/manifest-f.json index 302c508..aa58387 100644 --- a/duplicate-tabs-closer-master/manifest-f.json +++ b/duplicate-tabs-closer-master/manifest-f.json @@ -14,18 +14,12 @@ "strict_min_version": "101.0" } }, - "background": { - "scripts": [ - "helper.js", - "tabsInfo.js", - "options.js", - "urlUtils.js", - "badge.js", - "worker.js", - "messageListener.js", - "background.js" - ] - }, + "background": { + "scripts": [ + "helper.js", + "background.bundle.js" + ] + }, "browser_action": { "browser_style": false, "default_icon": { diff --git a/duplicate-tabs-closer-master/messageListener.js b/duplicate-tabs-closer-master/messageListener.js deleted file mode 100644 index febddab..0000000 --- a/duplicate-tabs-closer-master/messageListener.js +++ /dev/null @@ -1,24 +0,0 @@ -"use strict"; - -const handleMessage = (message, sender, response) => { - switch (message.action) { - case "setStoredOption": { - setStoredOption(message.data.name, message.data.value, message.data.refresh); - break; - } - case "getStoredOptions": { - getStoredOptions().then(storedOptions => response({ data: storedOptions })); - return true; - } - case "getDuplicateTabs": { - requestDuplicateTabsFromPanel(message.data.windowId); - break; - } - case "closeDuplicateTabs": { - closeDuplicateTabs(message.data.windowId); - break; - } - } -}; - -chrome.runtime.onMessage.addListener(handleMessage); \ No newline at end of file diff --git a/duplicate-tabs-closer-master/options.js b/duplicate-tabs-closer-master/options.js deleted file mode 100644 index 0b91dac..0000000 --- a/duplicate-tabs-closer-master/options.js +++ /dev/null @@ -1,207 +0,0 @@ -"use strict"; - -const defaultOptions = { - shrunkMode: { - value: false - }, - onDuplicateTabDetected: { - value: "N" - }, - onRemainingTab: { - value: "A" - }, - keepTabBasedOnAge: { - value: "O" - }, - keepTabWithHttps: { - value: true - }, - keepPinnedTab: { - value: true - }, - keepTabWithHistory: { - value: false - }, - scope: { - value: "C" - }, - ignoreHashPart: { - value: false - }, - ignoreSearchPart: { - value: false - }, - ignorePathPart: { - value: false - }, - ignore3w: { - value: false - }, - caseInsensitive: { - value: false - }, - compareWithTitle: { - value: false - }, - onDuplicateTabDetectedPinned: { - value: true - }, - tabPriorityPinned: { - value: true - }, - matchingRulesPinned: { - value: true - }, - scopePinned: { - value: true - }, - customizationPinned: { - value: true - }, - whiteList: { - value: "" - }, - blackList: { - value: "" - }, - badgeColorDuplicateTabs: { - value: "#f22121" - }, - badgeColorNoDuplicateTabs: { - value: "#1e90ff" - }, - showBadgeIfNoDuplicateTabs: { - value: true - }, - closePopup: { - value: false - }, - environment: { - value: "firefox" - } -}; - -const setupDefaultOptions = async () => { - const environment = await getEnvironment(); - const options = Object.assign({}, defaultOptions); - options.environment.value = environment; - return options; -}; - -const getEnvironment = async () => { - const info = await getPlatformInfo(); - const environment = (info.os === "android") ? "android" : (typeof InstallTrigger !== "undefined") ? "firefox" : "chrome"; - return environment; -}; - -const getNotInReferenceKeys = (referenceKeys, keys) => { - const setKeys = new Set(keys); - return Array.from(referenceKeys).filter(key => !setKeys.has(key)); -}; - -// eslint-disable-next-line no-unused-vars -const initializeOptions = async () => { - const options = await getStoredOptions(); - let storedOptions = options.storedOptions; - if (storedOptions.length === 0) { - const intialOptions = await setupDefaultOptions(); - storedOptions = await saveStoredOptions(intialOptions); - } else { - const storedKeys = Object.keys(storedOptions).sort(); - const defaultKeys = Object.keys(defaultOptions).sort(); - if (JSON.stringify(storedKeys) != JSON.stringify(defaultKeys)) { - const obsoleteKeys = getNotInReferenceKeys(storedKeys, defaultKeys); - obsoleteKeys.forEach(key => delete storedOptions[key]); - const missingKeys = getNotInReferenceKeys(defaultKeys, storedKeys); - // eslint-disable-next-line no-return-assign - missingKeys.forEach(key => storedOptions[key] = { value: defaultOptions[key].value }); - const environment = await getEnvironment(); - storedOptions.environment.value = environment; - storedOptions = await saveStoredOptions(storedOptions, true); - } - } - setOptions(storedOptions); - setEnvironment(storedOptions); -}; - -// eslint-disable-next-line no-unused-vars -const setStoredOption = async (name, value, refresh) => { - const options = await getStoredOptions(); - const storedOptions = options.storedOptions; - storedOptions[name].value = value; - saveStoredOptions(storedOptions); - setOptions(storedOptions); - if (refresh) refreshGlobalDuplicateTabsInfo(); - else if (name === "onDuplicateTabDetected") setBadgeIcon(); - else if (name === "showBadgeIfNoDuplicateTabs" || name === "badgeColorNoDuplicateTabs" || name === "badgeColorDuplicateTabs") updateBadgeStyle(); -}; - -const options = {}; - -const setOptions = (storedOptions) => { - options.autoCloseTab = storedOptions.onDuplicateTabDetected.value === "A"; - options.defaultTabBehavior = storedOptions.onRemainingTab.value === "B"; - options.activateKeptTab = storedOptions.onRemainingTab.value === "A"; - options.keepNewerTab = storedOptions.keepTabBasedOnAge.value === "N"; - options.keepReloadOlderTab = storedOptions.keepTabBasedOnAge.value === "R"; - options.keepTabWithHttps = storedOptions.keepTabWithHttps.value; - options.keepPinnedTab = storedOptions.keepPinnedTab.value; - options.ignoreHashPart = storedOptions.ignoreHashPart.value; - options.ignoreSearchPart = storedOptions.ignoreSearchPart.value; - options.ignorePathPart = storedOptions.ignorePathPart.value; - options.compareWithTitle = storedOptions.compareWithTitle.value; - options.ignore3w = storedOptions.ignore3w.value; - options.caseInsensitive = storedOptions.caseInsensitive.value; - options.searchInAllWindows = storedOptions.scope.value === "A" || storedOptions.scope.value === "CA"; - options.searchPerContainer = storedOptions.scope.value === "CC" || storedOptions.scope.value === "CA"; - options.whiteList = whiteListToPattern(storedOptions.whiteList.value); - options.badgeColorDuplicateTabs = storedOptions.badgeColorDuplicateTabs.value; - options.badgeColorNoDuplicateTabs = storedOptions.badgeColorNoDuplicateTabs.value; - options.showBadgeIfNoDuplicateTabs = storedOptions.showBadgeIfNoDuplicateTabs.value; -}; - -const environment = { - isAndroid: false, - isFirefox: false, - isChrome: false -}; - -const setEnvironment = (storedOptions) => { - if (storedOptions.environment.value === "android") { - environment.isAndroid = true; - environment.isFirefox = false; - } else if (storedOptions.environment.value === "firefox") { - environment.isAndroid = false; - environment.isFirefox = true; - environment.isChrome = false; - } - else if (storedOptions.environment.value === "chrome") { - environment.isAndroid = false; - environment.isFirefox = false; - environment.isChrome = true; - } -}; - -// eslint-disable-next-line no-unused-vars -const isPanelOptionOpen = () => { - return false; //override for now, until replacement API comes in - /* const popups = chrome.extension.getViews({ type: "popup" }); - if (popups.length) return true; - const tabs = chrome.extension.getViews({ type: "tab" }); - return tabs.length > 0; */ -}; - -const whiteListToPattern = (whiteList) => { - const whiteListPatterns = new Set(); - const whiteListLines = whiteList.split("\n").map(line => line.trim()); - whiteListLines.forEach(whiteListLine => { - const length = whiteListLine.length; - let pattern = "^"; - for (let index = 0; index < length; index += 1) { - const character = whiteListLine.charAt(index); - pattern = (character === "*") ? `${pattern}.*` : pattern + character; - } - whiteListPatterns.add(new RegExp(`${pattern}$`)); - }); - return Array.from(whiteListPatterns); -}; \ No newline at end of file diff --git a/duplicate-tabs-closer-master/package.json b/duplicate-tabs-closer-master/package.json index 03c387f..54b113e 100644 --- a/duplicate-tabs-closer-master/package.json +++ b/duplicate-tabs-closer-master/package.json @@ -2,7 +2,7 @@ "name": "duplicate-tabs-closer", "version": "1.0.0", "description": "Duplicate Tabs Closer detects and closes duplicate tabs.", - "main": "background.js", + "main": "background.bundle.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/duplicate-tabs-closer-master/scriptImport.js b/duplicate-tabs-closer-master/scriptImport.js index 56b3af8..f2feb67 100644 --- a/duplicate-tabs-closer-master/scriptImport.js +++ b/duplicate-tabs-closer-master/scriptImport.js @@ -1,5 +1,5 @@ -try { - importScripts("helper.js", "tabsInfo.js", "options.js", "urlUtils.js", "badge.js", "worker.js", "messageListener.js", "background.js"); -} catch (e) { - console.error(e); -} \ No newline at end of file +try { + importScripts("helper.js", "background.bundle.js"); +} catch (e) { + console.error(e); +} diff --git a/duplicate-tabs-closer-master/tabsInfo.js b/duplicate-tabs-closer-master/tabsInfo.js deleted file mode 100644 index 6efe4b0..0000000 --- a/duplicate-tabs-closer-master/tabsInfo.js +++ /dev/null @@ -1,88 +0,0 @@ -"use strict"; - -class TabsInfo { - - constructor() { - this.tabs = new Map(); - this.nbDuplicateTabs = new Map(); - this.initialize(); - } - - async initialize() { - const openedTabs = await getTabs({ windowType: "normal" }); - for (const openedTab of openedTabs) { - this.setOpenedTab(openedTab); - } - } - - setNewTab(tabId) { - const tab = { url: null, lastComplete: null, ignored: false }; - this.tabs.set(tabId, tab); - } - - setOpenedTab(openedTab) { - const tab = { url: openedTab.url, lastComplete: Date.now(), ignored: false }; - this.tabs.set(openedTab.id, tab); - } - - ignoreTab(tabId, state) { - const tab = this.tabs.get(tabId); - tab.ignored = state; - this.tabs.set(tabId, tab); - } - - isIgnoredTab(tabId) { - const tab = this.tabs.get(tabId); - return (!tab || tab.ignored) ? true : false; - } - - getLastComplete(tabId) { - const tab = this.tabs.get(tabId); - return tab.lastComplete; - } - - updateTab(openedTab) { - const tab = this.tabs.get(openedTab.id); - tab.url = openedTab.url; - tab.lastComplete = Date.now(); - this.tabs.set(openedTab.id, tab); - } - - resetTab(tabId) { - this.setNewTab(tabId); - } - - hasUrlChanged(openedTab) { - const tab = this.tabs.get(openedTab.id); - return tab.url !== openedTab.url; - } - - removeTab(tabId) { - this.tabs.delete(tabId); - } - - hasTab(tabId) { - return this.tabs.has(tabId); - } - - hasDuplicateTabs(windowId) { - // Even nothing set, return true so it will force the refresh and set the badge. - return this.nbDuplicateTabs.get(windowId) !== "0"; - } - - getNbDuplicateTabs(windowId) { - return this.nbDuplicateTabs.get(windowId) || "0"; - } - - setNbDuplicateTabs(windowId, nbDuplicateTabs) { - this.nbDuplicateTabs.set(windowId, nbDuplicateTabs.toString()); - } - - clearDuplicateTabsInfo(windowId) { - if (this.nbDuplicateTabs.has(windowId)) this.nbDuplicateTabs.delete(windowId); - } - -} - -// eslint-disable-next-line no-unused-vars -const tabsInfo = new TabsInfo(); \ No newline at end of file diff --git a/duplicate-tabs-closer-master/urlUtils.js b/duplicate-tabs-closer-master/urlUtils.js deleted file mode 100644 index 38366d5..0000000 --- a/duplicate-tabs-closer-master/urlUtils.js +++ /dev/null @@ -1,70 +0,0 @@ -"use strict"; - -// eslint-disable-next-line no-unused-vars -const isBlankURL = (url) => url === "about:blank"; - -// eslint-disable-next-line no-unused-vars -const isChromeURL = (url) => url.startsWith("chrome://") || url.startsWith("view-source:chrome-search"); - -const isBrowserURL = (url) => url.startsWith("about:") || url.startsWith("chrome://"); - -const isValidURL = (url) => { - const regex = /^(f|ht)tps?:\/\//i; - return regex.test(url); -}; - -// eslint-disable-next-line no-unused-vars -const isHttps = (url) => { - const regex = /^https:\/\//i; - return regex.test(url); -}; - -// eslint-disable-next-line no-unused-vars -const getMatchingURL = (url) => { - if (!isValidURL(url)) return url; - let matchingURL = url; - if (options.ignorePathPart) { - const uri = new URL(matchingURL); - matchingURL = uri.origin; - } - else if (options.ignoreSearchPart) { - matchingURL = matchingURL.split("?")[0]; - } - else if (options.ignoreHashPart) { - matchingURL = matchingURL.split("#")[0]; - } - if (options.keepTabWithHttps) { - matchingURL = matchingURL.replace(/^http:\/\//i, "https://"); - } - if (options.ignore3w) { - matchingURL = matchingURL.replace("://www.", "://"); - } - if (options.caseInsensitive) { - matchingURL = matchingURL.toLowerCase(); - } - matchingURL = matchingURL.replace(/\/$/, ""); - return matchingURL; -}; - -// eslint-disable-next-line no-unused-vars -const getMatchPatternURL = (url) => { - let urlPattern = null; - if (isValidURL(url)) { - const uri = new URL(url); - urlPattern = `*://${uri.hostname}`; - if (options.ignorePathPart) { - urlPattern += "/*"; - } - else { - urlPattern += uri.pathname; - if (uri.search || uri.hash) { - urlPattern += "*"; - } - } - } - else if (isBrowserURL(url)) { - urlPattern = `${url}*`; - } - - return urlPattern; -}; \ No newline at end of file diff --git a/duplicate-tabs-closer-master/worker.js b/duplicate-tabs-closer-master/worker.js deleted file mode 100644 index b5a72b3..0000000 --- a/duplicate-tabs-closer-master/worker.js +++ /dev/null @@ -1,301 +0,0 @@ -"use strict"; - -const isUrlWhiteListed = (url) => { - const matches = options.whiteList.filter(pattern => pattern.test(url)); - return matches.length !== 0; -}; - -const matchTitle = (tab1, tab2) => { - if (options.compareWithTitle) { - if ((isTabComplete(tab1) && isTabComplete(tab2)) && (tab1.title === tab2.title)) { - return true; - } - } - return false; -}; - -const getHttpsTabId = (observedTab, observedTabUrl, openedTab) => { - if (options.keepTabWithHttps) { - const regex = /^https:\/\//i; - const match1 = regex.test(observedTabUrl); - const match2 = regex.test(openedTab.url); - if (match1) { - return match2 ? null : observedTab.id; - } else { - return match2 ? openedTab.id : null; - } - } - return null; -}; - -const getPinnedTabId = (tab1, tab2) => { - if (options.keepPinnedTab) { - if (tab1.pinned) { - return tab2.pinned ? null : tab1.id; - } else { - return tab2.pinned ? tab2.id : null; - } - } - return null; -}; - -const getLastUpdatedTabId = (observedTab, openedTab) => { - const observedTabLastUpdate = tabsInfo.getLastComplete(observedTab.id); - const openedTabLastUpdate = tabsInfo.getLastComplete(openedTab.id); - if (options.keepNewerTab) { - if (observedTabLastUpdate === null) return observedTab.id; - if (openedTabLastUpdate === null) return openedTab.id; - return (observedTabLastUpdate > openedTabLastUpdate) ? observedTab.id : openedTab.id; - } else { - if (observedTabLastUpdate === null) return openedTab.id; - if (openedTabLastUpdate === null) return observedTab.id; - return (observedTabLastUpdate < openedTabLastUpdate) ? observedTab.id : openedTab.id; - } -}; - -const getFocusedTab = (observedTab, openedTab, activeWindowId, retainedTabId) => { - if (retainedTabId === observedTab.id) { - return ((openedTab.windowId === activeWindowId) && (openedTab.active || (observedTab.windowId !== activeWindowId)) ? openedTab.id : observedTab.id); - } - else { - return ((observedTab.windowId === activeWindowId) && (observedTab.active || (openedTab.windowId !== activeWindowId)) ? observedTab.id : openedTab.id); - } -}; - - -const getCloseInfo = (details) => { - const observedTab = details.observedTab; - const observedTabUrl = details.observedTabUrl || observedTab.url; - const openedTab = details.openedTab; - const activeWindowId = details.activeWindowId; - let retainedTabId = getPinnedTabId(observedTab, openedTab); - if (!retainedTabId) { - retainedTabId = getHttpsTabId(observedTab, observedTabUrl, openedTab); - if (!retainedTabId) { - retainedTabId = getLastUpdatedTabId(observedTab, openedTab); - if (activeWindowId) { - retainedTabId = getFocusedTab(observedTab, openedTab, activeWindowId, retainedTabId); - } - } - } - if (retainedTabId == observedTab.id) { - const keepInfo = { - observedTabClosed: false, - active: openedTab.active, - tabIndex: openedTab.index, - tabId: observedTab.id, - windowId: observedTab.windowId, - reloadTab: false - }; - return [openedTab.id, keepInfo]; - } else { - const keepInfo = { - observedTabClosed: true, - active: observedTab.active, - tabIndex: observedTab.index, - tabId: openedTab.id, - windowId: openedTab.windowId, - reloadTab: options.keepReloadOlderTab ? true : false - }; - return [observedTab.id, keepInfo]; - } -}; - -// eslint-disable-next-line no-unused-vars -const searchForDuplicateTabsToClose = async (observedTab, queryComplete, loadingUrl) => { - const observedTabUrl = loadingUrl || observedTab.url; - const observedWindowsId = observedTab.windowId; - if (isUrlWhiteListed(observedTabUrl)) { - if (isTabComplete(observedTab)) refreshDuplicateTabsInfo(observedWindowsId); - return; - } - const queryInfo = {}; - queryInfo.status = queryComplete ? "complete" : null; - queryInfo.url = getMatchPatternURL(observedTabUrl); - queryInfo.windowId = options.searchInAllWindows ? null : observedWindowsId; - if (environment.isFirefox) queryInfo.cookieStoreId = options.searchPerContainer ? observedTab.cookieStoreId : null; - const openedTabs = await getTabs(queryInfo); - if (openedTabs.length > 1) { - const matchingObservedTabUrl = getMatchingURL(observedTabUrl); - let match = false; - for (const openedTab of openedTabs) { - if ((openedTab.id === observedTab.id) || tabsInfo.isIgnoredTab(openedTab.id) || (isBlankURL(openedTab.url) && !isTabComplete(openedTab))) continue; - if ((getMatchingURL(openedTab.url) === matchingObservedTabUrl) || matchTitle(openedTab, observedTab)) { - match = true; - const [tabToCloseId, remainingTabInfo] = getCloseInfo({ observedTab: observedTab, observedTabUrl: observedTabUrl, openedTab: openedTab }); - closeDuplicateTab(tabToCloseId, remainingTabInfo); - if (remainingTabInfo.observedTabClosed) break; - } - } - if (!match) { - if (tabsInfo.hasDuplicateTabs(observedWindowsId)) refreshDuplicateTabsInfo(observedWindowsId); - else if (environment.isChrome && observedTab.active) setBadge(observedTab.windowId, observedTab.id); - } - } -}; - -const closeDuplicateTab = async (tabToCloseId, remainingTabInfo) => { - try { - tabsInfo.ignoreTab(tabToCloseId, true); - await removeTab(tabToCloseId); - } - catch (ex) { - tabsInfo.ignoreTab(tabToCloseId, false); - return; - } - if (tabsInfo.hasTab(tabToCloseId)) { - await wait(10); - if (tabsInfo.hasTab(tabToCloseId)) { - tabsInfo.ignoreTab(tabToCloseId, false); - refreshDuplicateTabsInfo(remainingTabInfo.windowId); - return; - } - } - handleRemainingTab(remainingTabInfo.windowId, remainingTabInfo); -}; - -const _handleRemainingTab = async (details) => { - if (!tabsInfo.hasTab(details.tabId)) return; - if (options.defaultTabBehavior && details.observedTabClosed) { - if (details.tabIndex > 0) moveTab(details.tabId, { index: details.tabIndex }); - if (details.active) activateTab(details.tabId); - } else if (options.activateKeptTab) { - focusTab(details.tabId, details.windowId); - } - if (details.reloadTab) { - tabsInfo.ignoreTab(details.tabId, true); - await reloadTab(details.tabId); - tabsInfo.ignoreTab(details.tabId, false); - } -}; - -const handleRemainingTab = debounce(_handleRemainingTab, 500); - -const handleObservedTab = (details) => { - const observedTab = details.tab; - const retainedTabs = details.retainedTabs; - const duplicateTabsGroups = details.duplicateTabsGroups; - let matchingTabURL = getMatchingURL(observedTab.url); - let matchingTabTitle = options.compareWithTitle && isTabComplete(observedTab) ? `title=${observedTab.title}` : null; - if (options.searchPerContainer) { - matchingTabURL += observedTab.cookieStoreId; - if (matchingTabTitle) matchingTabTitle += observedTab.cookieStoreId; - } - let matchingKey = matchingTabURL; - let retainedTab = retainedTabs.get(matchingKey); - if (!retainedTab) { - if (isTabComplete(observedTab)) retainedTabs.set(matchingKey, observedTab); - if (matchingTabTitle) { - matchingKey = matchingTabTitle; - retainedTab = retainedTabs.get(matchingKey); - if (!retainedTab) { - retainedTabs.set(matchingKey, observedTab); - } - } - } - if (retainedTab) { - if (details.closeTab) { - const [tabToCloseId] = getCloseInfo({ observedTab: observedTab, openedTab: retainedTab, activeWindowId: details.activeWindowId }); - if (tabToCloseId === observedTab.id) { - chrome.tabs.remove(observedTab.id); - } - else { - chrome.tabs.remove(retainedTab.id); - retainedTabs.set(matchingKey, observedTab); - } - } else { - const tabs = duplicateTabsGroups.get(matchingKey) || new Set([retainedTab]); - tabs.add(observedTab); - duplicateTabsGroups.set(matchingKey, tabs); - } - } -}; - -// eslint-disable-next-line no-unused-vars -const searchForDuplicateTabs = async (windowId, closeTabs) => { - const queryInfo = { windowType: "normal" }; - if (!options.searchInAllWindows) queryInfo.windowId = windowId; - const [activeWindowId, openedTabs] = await Promise.all([getActiveWindowId(), getTabs(queryInfo)]); - const duplicateTabsGroups = new Map(); - const retainedTabs = new Map(); - for (const openedTab of openedTabs) { - if ((isBlankURL(openedTab.url) && !isTabComplete(openedTab)) || tabsInfo.isIgnoredTab(openedTab.id)) continue; - const details = { - tab: openedTab, - retainedTabs: retainedTabs, - activeWindowId: activeWindowId, - closeTab: closeTabs, - duplicateTabsGroups: duplicateTabsGroups - }; - handleObservedTab(details); - } - if (!closeTabs) { - return { - duplicateTabsGroups: duplicateTabsGroups, - activeWindowId: activeWindowId - }; - } -}; - -// eslint-disable-next-line no-unused-vars -const closeDuplicateTabs = (windowId) => searchForDuplicateTabs(windowId, true); - -const setDuplicateTabPanel = async (duplicateTab, duplicateTabs) => { - let containerColor = ""; - if (environment.isFirefox && (!duplicateTab.incognito && duplicateTab.cookieStoreId !== "firefox-default")) { - const getContext = await browser.contextualIdentities.get(duplicateTab.cookieStoreId); - if (getContext) containerColor = getContext.color; - } - duplicateTabs.add({ - id: duplicateTab.id, - url: duplicateTab.url, - title: duplicateTab.title || duplicateTab.url, - windowId: duplicateTab.windowId, - containerColor: containerColor, - icon: duplicateTab.favIconUrl || "../images/default-favicon.png" - }); -}; - -const getDuplicateTabsForPanel = async (duplicateTabsGroups) => { - if (duplicateTabsGroups.size === 0) return null; - const duplicateTabsPanel = new Set(); - for (const tabsGroup of duplicateTabsGroups) { - const duplicateTabs = tabsGroup[1]; - await Promise.all(Array.from(duplicateTabs, duplicateTab => setDuplicateTabPanel(duplicateTab, duplicateTabsPanel))); - } - return Array.from(duplicateTabsPanel); -}; - -// eslint-disable-next-line no-unused-vars -const requestDuplicateTabsFromPanel = async (windowId) => { - const searchResult = await searchForDuplicateTabs(windowId, false); - sendDuplicateTabs(searchResult.duplicateTabsGroups); -}; - -const sendDuplicateTabs = async (duplicateTabsGroups) => { - const duplicateTabs = await getDuplicateTabsForPanel(duplicateTabsGroups); - chrome.runtime.sendMessage({ - action: "updateDuplicateTabsTable", - data: { "duplicateTabs": duplicateTabs } - }); -}; - -const _refreshDuplicateTabsInfo = async (windowId) => { - const searchResult = await searchForDuplicateTabs(windowId, false); - updateBadgesValue(searchResult.duplicateTabsGroups, windowId); - if (isPanelOptionOpen() && (options.searchInAllWindows || (windowId === searchResult.activeWindowId))) { - sendDuplicateTabs(searchResult.duplicateTabsGroups); - } -}; - -const refreshDuplicateTabsInfo = debounce(_refreshDuplicateTabsInfo, 300); - -// eslint-disable-next-line no-unused-vars -const refreshGlobalDuplicateTabsInfo = async () => { - if (options.searchInAllWindows) { - refreshDuplicateTabsInfo(); - } else { - const windows = await getWindows(); - windows.forEach(window => refreshDuplicateTabsInfo(window.id)); - } -}; \ No newline at end of file