From c81ce93d02ce6bd0cda6410898f32653ddef4eb5 Mon Sep 17 00:00:00 2001 From: Federico Date: Fri, 31 Jan 2025 16:02:25 +0900 Subject: [PATCH 1/4] Drop `activeTab` support --- .npmignore | 1 - package.json | 7 +-- readme.md | 13 ---- source/active-tab.ts | 88 --------------------------- source/including-active-tab.ts | 74 ---------------------- source/simple-event-target.ts | 44 -------------- test/demo-extension/mv2/background.js | 14 +---- test/demo-extension/mv2/manifest.json | 2 - test/demo-extension/mv3/background.js | 14 +---- test/demo-extension/mv3/manifest.json | 2 +- 10 files changed, 4 insertions(+), 255 deletions(-) delete mode 100644 source/active-tab.ts delete mode 100644 source/including-active-tab.ts delete mode 100644 source/simple-event-target.ts diff --git a/.npmignore b/.npmignore index be5c3c2..60fad98 100644 --- a/.npmignore +++ b/.npmignore @@ -2,4 +2,3 @@ !/distribution/* *.test.* !/utils.* -!/including-active-tab.* diff --git a/package.json b/package.json index 3721903..2be0010 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "webext-dynamic-content-scripts", "version": "10.0.4", - "description": "WebExtension module: Automatically registers your `content_scripts` on domains added via `permission.request` or on `activeTab`", + "description": "WebExtension module: Automatically registers your `content_scripts` on domains added via `permission.request`", "keywords": [ "contentscript", "register", @@ -14,7 +14,6 @@ "chrome", "firefox", "browser", - "activetab", "extension" ], "repository": "fregante/webext-dynamic-content-scripts", @@ -27,10 +26,6 @@ "types": "./distribution/index.d.ts", "default": "./distribution/index.js" }, - "./including-active-tab.js": { - "types": "./distribution/including-active-tab.d.ts", - "default": "./distribution/including-active-tab.js" - }, "./utils.js": { "types": "./distribution/utils.d.ts", "default": "./distribution/utils.js" diff --git a/readme.md b/readme.md index d6600cf..aac073e 100644 --- a/readme.md +++ b/readme.md @@ -59,19 +59,6 @@ navigator.importScripts('webext-dynamic-content-scripts.js'); } ``` -### `activeTab` tracking - -By default, the module will only inject the content scripts into newly-permitted hosts, but it will ignore temporary permissions like `activeTab`. If you also want to automatically inject the content scripts into every frame of tabs as soon as they receive the `activeTab` permission, import a different entry point **instead of the default one.** - -```js -import 'webext-dynamic-content-scripts/including-active-tab.js'; -``` - -> **Note** -> This does not work well in Firefox because of some compounding bugs: -> - `activeTab` seems to be lost after a reload -> - further `contextMenu` clicks receive a moz-extension URL rather than the current page’s URL - ### Additional APIs #### `isContentScriptRegistered(url)` diff --git a/source/active-tab.ts b/source/active-tab.ts deleted file mode 100644 index f24e86a..0000000 --- a/source/active-tab.ts +++ /dev/null @@ -1,88 +0,0 @@ -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'); -} - -type TabId = number; -type Origin = string; - -export type ActiveTab = { - id: TabId; - origin: Origin; -}; - -const newActiveTabs = new SimpleEventTarget(); - -const browserAction = chrome.action ?? chrome.browserAction; - -export const possiblyActiveTabs = new Map(); - -async function addIfScriptable({url, id}: chrome.tabs.Tab): Promise { - if ( - id && url - - // Skip if it already exists. A previous change of origin already cleared this - && !possiblyActiveTabs.has(id) - - // ActiveTab makes 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: number, {url}: chrome.tabs.TabChangeInfo): void { - 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(_: unknown, tab?: chrome.tabs.Tab): void { - if (tab) { - void addIfScriptable(tab); - } -} - -function drop(tabId: TabId): void { - console.debug('activeTab:', tabId, 'removed'); - possiblyActiveTabs.delete(tabId); -} - -// https://developer.chrome.com/docs/extensions/mv3/manifest/activeTab/#invoking-activeTab -export function startActiveTabTracking(): void { - 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(): void { - 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: (tab: ActiveTab) => void): void { - startActiveTabTracking(); - newActiveTabs.add(callback); -} diff --git a/source/including-active-tab.ts b/source/including-active-tab.ts deleted file mode 100644 index 2e00440..0000000 --- a/source/including-active-tab.ts +++ /dev/null @@ -1,74 +0,0 @@ -import {type ContentScript} from 'webext-content-scripts/types'; -import {injectContentScript} from 'webext-content-scripts'; -import chromeP from 'webext-polyfill-kinda'; -import {type ActiveTab, addActiveTabListener, possiblyActiveTabs} from './active-tab.js'; -import {isContentScriptRegistered} from './utils.js'; -import './index.js'; // Core functionality - -type InjectionDetails = { - tabId: number; - frameId: number; - url: string; -}; - -const gotNavigation = typeof chrome === 'object' && 'webNavigation' in chrome; - -const scripts = chrome.runtime.getManifest().content_scripts as ContentScript; - -async function injectToTabUnlessRegistered({id: tabId, origin}: ActiveTab): Promise { - if (tabId === undefined) { - return; - } - - const frames = gotNavigation - // Only with `webNavigation` we can inject into the frames - ? await chromeP.webNavigation.getAllFrames({tabId}) - - // Without it, we only inject it into the top frame - : [{frameId: 0, url: origin}]; - - // .map() needed for async loop - void frames?.map(async ({frameId, url}) => injectIfActive({frameId, url, tabId})); -} - -async function injectIfActive( - {tabId, frameId, url}: InjectionDetails, -): Promise { - const {origin} = new URL(url); - if ( - // Check origin because the request might be for a frame; cross-origin frames do not receive activeTab - possiblyActiveTabs.get(tabId) === origin - - // Don't inject if already registered - && !(await isContentScriptRegistered(url)) - ) { - console.debug('activeTab: will inject', {tabId, frameId, url}); - await injectContentScript({tabId, frameId}, scripts); - } else { - console.debug('activeTab: won’t inject', {tabId, frameId, url}, {activeTab: possiblyActiveTabs.get(tabId) ?? 'no'}); - } -} - -async function tabListener( - tabId: number, - {status}: chrome.tabs.TabChangeInfo, - {url}: chrome.tabs.Tab, -): Promise { - // Only status updates are relevant - // No URL = no permission - if (status === 'loading' && url) { - await injectIfActive({tabId, url, frameId: 0}); - } -} - -function init() { - addActiveTabListener(injectToTabUnlessRegistered); - - if (gotNavigation) { - chrome.webNavigation.onCommitted.addListener(injectIfActive); - } else { - chrome.tabs.onUpdated.addListener(tabListener); - } -} - -init(); diff --git a/source/simple-event-target.ts b/source/simple-event-target.ts deleted file mode 100644 index 6e7cead..0000000 --- a/source/simple-event-target.ts +++ /dev/null @@ -1,44 +0,0 @@ -type SimpleEventListener = (detail: Detail) => void; - -/** - * Thinnest possible wrapper around native events - * - * @usage - * const smokeSignals = new SimpleEventTarget(); - * smokeSignals.add(details => console.log(details)) - * smokeSignals.emit('The BBQ is ready'); - */ -export class SimpleEventTarget extends EventTarget { - coreEvent = 'DEFAULT'; - private readonly weakEvents = new WeakMap, EventListener>(); - - add(callback: SimpleEventListener): void { - this.addEventListener(this.coreEvent, this.getNativeListener(callback)); - } - - remove(callback: SimpleEventListener): void { - this.removeEventListener(this.coreEvent, this.getNativeListener(callback)); - } - - emit(detail?: Detail): void { - this.dispatchEvent(new CustomEvent(this.coreEvent, {detail})); - } - - // Permanently map simplified callbacks to native listeners. - // This acts as a memoization/deduplication which matches the native behavior. - // Calling `add(cb); add(cb); remove(cb)` should only add it once and remove it once. - private getNativeListener( - callback: SimpleEventListener, - ): EventListener { - if (this.weakEvents.has(callback)) { - return this.weakEvents.get(callback)!; - } - - const native = ((event: CustomEvent) => { - callback(event.detail); - }) as EventListener; - - this.weakEvents.set(callback, native); - return native; - } -} diff --git a/test/demo-extension/mv2/background.js b/test/demo-extension/mv2/background.js index 6371252..9096e1c 100644 --- a/test/demo-extension/mv2/background.js +++ b/test/demo-extension/mv2/background.js @@ -1,15 +1,3 @@ -import 'webext-dynamic-content-scripts/including-active-tab.ts'; - -chrome.contextMenus.create({ - title: 'Enable activeTab permission', -}, () => { - if (chrome.runtime.lastError) { - // Shush - } -}); - -chrome.contextMenus.onClicked.addListener(() => { - console.log('Context menu clicked'); -}); +import 'webext-dynamic-content-scripts'; console.log('Background loaded'); diff --git a/test/demo-extension/mv2/manifest.json b/test/demo-extension/mv2/manifest.json index 4a9c8e8..7a705ac 100644 --- a/test/demo-extension/mv2/manifest.json +++ b/test/demo-extension/mv2/manifest.json @@ -3,9 +3,7 @@ "version": "0.0.0", "manifest_version": 2, "permissions": [ - "activeTab", "webNavigation", - "contextMenus", "https://dynamic-ephiframe.vercel.app/*", "https://accepted-ephiframe.vercel.app/*" ], diff --git a/test/demo-extension/mv3/background.js b/test/demo-extension/mv3/background.js index 6371252..9096e1c 100644 --- a/test/demo-extension/mv3/background.js +++ b/test/demo-extension/mv3/background.js @@ -1,15 +1,3 @@ -import 'webext-dynamic-content-scripts/including-active-tab.ts'; - -chrome.contextMenus.create({ - title: 'Enable activeTab permission', -}, () => { - if (chrome.runtime.lastError) { - // Shush - } -}); - -chrome.contextMenus.onClicked.addListener(() => { - console.log('Context menu clicked'); -}); +import 'webext-dynamic-content-scripts'; console.log('Background loaded'); diff --git a/test/demo-extension/mv3/manifest.json b/test/demo-extension/mv3/manifest.json index 783c90b..b753d4c 100644 --- a/test/demo-extension/mv3/manifest.json +++ b/test/demo-extension/mv3/manifest.json @@ -2,7 +2,7 @@ "name": "webext-dynamic-content-scripts-mv3", "version": "0.0.0", "manifest_version": 3, - "permissions": ["webNavigation", "scripting", "contextMenus", "activeTab", "storage"], + "permissions": ["webNavigation", "scripting", "storage"], "host_permissions": [ "https://dynamic-ephiframe.vercel.app/*", "https://accepted-ephiframe.vercel.app/*" From 8eb5ff2462f2daf83d07d4eab9a41cc7bd3bdd06 Mon Sep 17 00:00:00 2001 From: Federico Date: Sun, 2 Feb 2025 03:52:35 +0800 Subject: [PATCH 2/4] drop URLs --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 2be0010..075d6c8 100644 --- a/package.json +++ b/package.json @@ -132,9 +132,6 @@ "https://dynamic-ephiframe.vercel.app/Dynamic?iframe=./Inner", "https://dynamic-ephiframe.vercel.app/Dynamic?iframe=https://static-ephiframe.vercel.app/Static-inner", "https://static-ephiframe.vercel.app/Static?iframe=https://dynamic-ephiframe.vercel.app/Dynamic-inner", - "https://extra-ephiframe.vercel.app/Inject-via-context-menu-please", - "https://extra-ephiframe.vercel.app/Inject-via-context-menu-please?iframe=https://static-ephiframe.vercel.app/Static-inner", - "https://extra-ephiframe.vercel.app/Inject-via-context-menu-please?iframe=https://dynamic-ephiframe.vercel.app/Dynamic-inner", "chrome://extensions/" ] } From 98a41df128236b8d6ce1044654637d7130cfd760 Mon Sep 17 00:00:00 2001 From: Federico Date: Sun, 2 Feb 2025 03:55:33 +0800 Subject: [PATCH 3/4] fix tests --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f88ddc3..a7b718e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,8 @@ jobs: - run: npm run vitest Test: - runs-on: ubuntu-latest + # https://github.com/puppeteer/puppeteer/issues/12818#issuecomment-2415915338 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: From f6cc30e1845e299d216c645531b3103101e62040 Mon Sep 17 00:00:00 2001 From: Federico Date: Sun, 2 Feb 2025 04:04:36 +0800 Subject: [PATCH 4/4] esmlint --- .github/workflows/esm-lint.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/esm-lint.yml b/.github/workflows/esm-lint.yml index b4c8a07..bd27b29 100644 --- a/.github/workflows/esm-lint.yml +++ b/.github/workflows/esm-lint.yml @@ -58,7 +58,7 @@ jobs: needs: Pack steps: - uses: actions/download-artifact@v4 - - run: npm install --omit=dev ./artifact rollup@2 @rollup/plugin-node-resolve + - run: npm install --omit=dev ./artifact rollup@4 @rollup/plugin-node-resolve - run: echo "$IMPORT_STATEMENT" > index.js - run: npx rollup -p node-resolve index.js Vite: @@ -84,10 +84,11 @@ jobs: needs: Pack steps: - uses: actions/download-artifact@v4 + - run: echo '{"type":"module"}' > package.json - run: npm install --omit=dev ./artifact @sindresorhus/tsconfig - - run: echo "$IMPORT_STATEMENT" > index.ts + - run: echo "$IMPORT_STATEMENT" > index.mts - run: > - echo '{"extends":"@sindresorhus/tsconfig","files":["index.ts"]}' > + echo '{"extends":"@sindresorhus/tsconfig","files":["index.mts"]}' > tsconfig.json - run: npx --package typescript -- tsc - - run: cat distribution/index.js + - run: cat distribution/index.mjs