webext-active-tab
Version:
WebExtension module: Track `activeTab` permission; automatically inject content scripts
66 lines (65 loc) • 2.68 kB
JavaScript
import { isScriptableUrl } from 'webext-content-scripts';
import { isBackground } from 'webext-detect';
import SimpleEventTarget from 'simple-event-target';
if (!isBackground()) {
throw new Error('This module is only allowed in a background script');
}
const newActiveTabs = new SimpleEventTarget();
const browserAction = chrome.action ?? chrome.browserAction;
// TODO: use webext-storage
export const possiblyActiveTabs = new Map();
async function addIfScriptable({ url, id }) {
if (id && url
// Skip if it already exists. A previous change of origin already cleared this
&& !possiblyActiveTabs.has(id)
// ActiveTab doesn't make sense on non-scriptable URLs as they generally don't have scriptable frames
&& isScriptableUrl(url)
// Note: Do not filter by `isContentScriptRegistered`; `active-tab` also applies to random `executeScript` calls
) {
const { origin } = new URL(url);
console.debug('activeTab:', id, 'added', { origin });
possiblyActiveTabs.set(id, origin);
newActiveTabs.emit({ id, origin });
}
else {
console.debug('activeTab:', id, 'not added', { origin });
}
}
function dropIfOriginChanged(tabId, { url }) {
if (url && possiblyActiveTabs.has(tabId)) {
const { origin } = new URL(url);
if (possiblyActiveTabs.get(tabId) !== origin) {
console.debug('activeTab:', tabId, 'removed because origin changed from', possiblyActiveTabs.get(tabId), 'to', origin);
possiblyActiveTabs.delete(tabId);
}
}
}
function altListener(_, tab) {
if (tab) {
void addIfScriptable(tab);
}
}
function drop(tabId) {
console.debug('activeTab:', tabId, 'removed');
possiblyActiveTabs.delete(tabId);
}
// https://developer.chrome.com/docs/extensions/mv3/manifest/activeTab/#invoking-activeTab
export function startActiveTabTracking() {
browserAction?.onClicked.addListener(addIfScriptable);
chrome.contextMenus?.onClicked.addListener(altListener);
chrome.commands?.onCommand.addListener(altListener);
chrome.tabs.onUpdated.addListener(dropIfOriginChanged);
chrome.tabs.onRemoved.addListener(drop);
}
export function stopActiveTabTracking() {
browserAction?.onClicked.removeListener(addIfScriptable);
chrome.contextMenus?.onClicked.removeListener(altListener);
chrome.commands?.onCommand.removeListener(altListener);
chrome.tabs.onUpdated.removeListener(dropIfOriginChanged);
chrome.tabs.onRemoved.removeListener(drop);
possiblyActiveTabs.clear();
}
export function addActiveTabListener(callback) {
startActiveTabTracking();
newActiveTabs.listen(callback);
}