webext-active-tab 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/distribution/on-active-tab.d.ts +25 -0
- package/distribution/on-active-tab.js +87 -0
- package/distribution/register-active-tab-scripts.d.ts +8 -0
- package/distribution/register-active-tab-scripts.js +44 -0
- package/distribution/register.d.ts +1 -0
- package/distribution/register.js +2 -0
- package/distribution/scripts.d.ts +1 -0
- package/distribution/scripts.js +47 -0
- package/distribution/tracker.d.ts +11 -0
- package/distribution/tracker.js +65 -0
- package/distribution/utils.d.ts +7 -0
- package/distribution/utils.js +30 -0
- package/license +9 -0
- package/package.json +102 -0
- package/readme.md +36 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type TabId = number;
|
|
2
|
+
type Origin = string;
|
|
3
|
+
export type ActiveTab = {
|
|
4
|
+
id: TabId;
|
|
5
|
+
origin: Origin;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* The list is not guaranteed to be up to date. The activeTab permission might be lost before it's detected.
|
|
9
|
+
*/
|
|
10
|
+
export declare const possiblyActiveTabs: Map<number, string>;
|
|
11
|
+
declare function startActiveTabTracking(): void;
|
|
12
|
+
declare function stopActiveTabTracking(): void;
|
|
13
|
+
export declare const PRIVATE: {
|
|
14
|
+
startActiveTabTracking: typeof startActiveTabTracking;
|
|
15
|
+
stopActiveTabTracking: typeof stopActiveTabTracking;
|
|
16
|
+
};
|
|
17
|
+
export declare const onActiveTab: {
|
|
18
|
+
addListener(callback: (tab: ActiveTab) => void): void;
|
|
19
|
+
removeListener: (callback: import("simple-event-target").SimpleEventListener<ActiveTab>) => void;
|
|
20
|
+
};
|
|
21
|
+
export declare const onActiveTabLost: {
|
|
22
|
+
addListener(callback: (tabId: TabId) => void): void;
|
|
23
|
+
removeListener: (callback: import("simple-event-target").SimpleEventListener<number>) => void;
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { isScriptableUrl } from 'webext-content-scripts';
|
|
2
|
+
import { isBackground } from 'webext-detect';
|
|
3
|
+
import SimpleEventTarget from 'simple-event-target';
|
|
4
|
+
if (!isBackground()) {
|
|
5
|
+
throw new Error('This module is only allowed in a background script');
|
|
6
|
+
}
|
|
7
|
+
if (!chrome.runtime.getManifest().permissions?.includes('activeTab')) {
|
|
8
|
+
throw new Error('The manifest should include the `activeTab` permission for `webext-active-tab` to work');
|
|
9
|
+
}
|
|
10
|
+
const activation = new SimpleEventTarget();
|
|
11
|
+
const deactivation = new SimpleEventTarget();
|
|
12
|
+
const browserAction = chrome.action ?? chrome.browserAction;
|
|
13
|
+
// TODO: use https://github.com/fregante/webext-storage/issues/5
|
|
14
|
+
/**
|
|
15
|
+
* The list is not guaranteed to be up to date. The activeTab permission might be lost before it's detected.
|
|
16
|
+
*/
|
|
17
|
+
export const possiblyActiveTabs = new Map();
|
|
18
|
+
async function addIfScriptable({ url, id }) {
|
|
19
|
+
if (id && url
|
|
20
|
+
// Skip if it already exists. A previous change of origin already cleared this
|
|
21
|
+
&& !possiblyActiveTabs.has(id)
|
|
22
|
+
// ActiveTab doesn't make sense on non-scriptable URLs as they generally don't have scriptable frames
|
|
23
|
+
&& isScriptableUrl(url)
|
|
24
|
+
// Note: Do not filter by `isContentScriptRegistered`; `active-tab` also applies to random `executeScript` calls
|
|
25
|
+
) {
|
|
26
|
+
const { origin } = new URL(url);
|
|
27
|
+
console.debug('activeTab:', id, 'added', { origin });
|
|
28
|
+
possiblyActiveTabs.set(id, origin);
|
|
29
|
+
activation.emit({ id, origin });
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.debug('activeTab:', id, 'not added', { origin });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function dropIfOriginChanged(tabId, { url }) {
|
|
36
|
+
if (url && possiblyActiveTabs.has(tabId)) {
|
|
37
|
+
const { origin } = new URL(url);
|
|
38
|
+
if (possiblyActiveTabs.get(tabId) !== origin) {
|
|
39
|
+
console.debug('activeTab:', tabId, 'removed because origin changed from', possiblyActiveTabs.get(tabId), 'to', origin);
|
|
40
|
+
possiblyActiveTabs.delete(tabId);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function altListener(_, tab) {
|
|
45
|
+
if (tab) {
|
|
46
|
+
void addIfScriptable(tab);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function drop(tabId) {
|
|
50
|
+
console.debug('activeTab:', tabId, 'removed');
|
|
51
|
+
possiblyActiveTabs.delete(tabId);
|
|
52
|
+
deactivation.emit(tabId);
|
|
53
|
+
}
|
|
54
|
+
// https://developer.chrome.com/docs/extensions/mv3/manifest/activeTab/#invoking-activeTab
|
|
55
|
+
function startActiveTabTracking() {
|
|
56
|
+
browserAction?.onClicked.addListener(addIfScriptable);
|
|
57
|
+
chrome.contextMenus?.onClicked.addListener(altListener);
|
|
58
|
+
chrome.commands?.onCommand.addListener(altListener);
|
|
59
|
+
chrome.tabs.onUpdated.addListener(dropIfOriginChanged);
|
|
60
|
+
chrome.tabs.onRemoved.addListener(drop);
|
|
61
|
+
}
|
|
62
|
+
function stopActiveTabTracking() {
|
|
63
|
+
browserAction?.onClicked.removeListener(addIfScriptable);
|
|
64
|
+
chrome.contextMenus?.onClicked.removeListener(altListener);
|
|
65
|
+
chrome.commands?.onCommand.removeListener(altListener);
|
|
66
|
+
chrome.tabs.onUpdated.removeListener(dropIfOriginChanged);
|
|
67
|
+
chrome.tabs.onRemoved.removeListener(drop);
|
|
68
|
+
possiblyActiveTabs.clear();
|
|
69
|
+
}
|
|
70
|
+
export const PRIVATE = {
|
|
71
|
+
startActiveTabTracking,
|
|
72
|
+
stopActiveTabTracking,
|
|
73
|
+
};
|
|
74
|
+
export const onActiveTab = {
|
|
75
|
+
addListener(callback) {
|
|
76
|
+
startActiveTabTracking();
|
|
77
|
+
activation.subscribe(callback);
|
|
78
|
+
},
|
|
79
|
+
removeListener: activation.unsubscribe.bind(activation),
|
|
80
|
+
};
|
|
81
|
+
export const onActiveTabLost = {
|
|
82
|
+
addListener(callback) {
|
|
83
|
+
startActiveTabTracking();
|
|
84
|
+
deactivation.subscribe(callback);
|
|
85
|
+
},
|
|
86
|
+
removeListener: deactivation.unsubscribe.bind(deactivation),
|
|
87
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type RunAt } from 'webext-content-scripts';
|
|
2
|
+
export type ActiveTabContentScript = {
|
|
3
|
+
css?: string[];
|
|
4
|
+
js?: string[];
|
|
5
|
+
matchAboutBlank?: boolean;
|
|
6
|
+
runAt?: RunAt;
|
|
7
|
+
};
|
|
8
|
+
export default function registerActiveTabScripts(...newScripts: ActiveTabContentScript[]): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { injectContentScript } from 'webext-content-scripts';
|
|
2
|
+
import { onActiveTab, possiblyActiveTabs } from './on-active-tab.js';
|
|
3
|
+
const gotNavigation = typeof chrome === 'object' && 'webNavigation' in chrome;
|
|
4
|
+
const scripts = [];
|
|
5
|
+
async function injectAllFrames({ id: tabId, origin: url }) {
|
|
6
|
+
if (tabId === undefined) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (!gotNavigation) {
|
|
10
|
+
// Without it, we only inject it into the top frame
|
|
11
|
+
return injectOneFrame({ frameId: 0, url, tabId });
|
|
12
|
+
}
|
|
13
|
+
const frames = await chrome.webNavigation.getAllFrames({ tabId });
|
|
14
|
+
// .map() needed for async loop
|
|
15
|
+
void frames?.map(async ({ frameId, url }) => injectOneFrame({ frameId, url, tabId }));
|
|
16
|
+
}
|
|
17
|
+
async function injectOneFrame({ tabId, frameId, url }) {
|
|
18
|
+
const { origin } = new URL(url);
|
|
19
|
+
// Check origin because the request might be for a frame; cross-origin frames do not receive activeTab
|
|
20
|
+
if (possiblyActiveTabs.get(tabId) === origin) {
|
|
21
|
+
console.debug('activeTab: will inject', { tabId, frameId, url });
|
|
22
|
+
await injectContentScript({ tabId, frameId }, scripts);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.debug('activeTab: won’t inject', { tabId, frameId, url }, { activeTab: possiblyActiveTabs.get(tabId) ?? 'no' });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function tabListener(tabId, { status }, { url }) {
|
|
29
|
+
// Only status updates are relevant
|
|
30
|
+
// No URL = no permission
|
|
31
|
+
if (status === 'loading' && url) {
|
|
32
|
+
await injectOneFrame({ tabId, url, frameId: 0 });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export default function registerActiveTabScripts(...newScripts) {
|
|
36
|
+
scripts.push(...newScripts);
|
|
37
|
+
onActiveTab.addListener(injectAllFrames);
|
|
38
|
+
if (gotNavigation) {
|
|
39
|
+
chrome.webNavigation.onCommitted.addListener(injectOneFrame);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
chrome.tabs.onUpdated.addListener(tabListener);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function register(): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { injectContentScript } from 'webext-content-scripts';
|
|
2
|
+
import { addActiveTabListener, possiblyActiveTabs } from './tracker.js';
|
|
3
|
+
import { isContentScriptRegistered } from './utils.js';
|
|
4
|
+
const gotNavigation = typeof chrome === 'object' && 'webNavigation' in chrome;
|
|
5
|
+
const scripts = chrome.runtime.getManifest().content_scripts;
|
|
6
|
+
async function injectToTabUnlessRegistered({ id: tabId, origin }) {
|
|
7
|
+
if (tabId === undefined) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const frames = gotNavigation
|
|
11
|
+
// Only with `webNavigation` we can inject into the frames
|
|
12
|
+
? await chrome.webNavigation.getAllFrames({ tabId })
|
|
13
|
+
// Without it, we only inject it into the top frame
|
|
14
|
+
: [{ frameId: 0, url: origin }];
|
|
15
|
+
// .map() needed for async loop
|
|
16
|
+
void frames?.map(async ({ frameId, url }) => injectIfActive({ frameId, url, tabId }));
|
|
17
|
+
}
|
|
18
|
+
async function injectIfActive({ tabId, frameId, url }) {
|
|
19
|
+
const { origin } = new URL(url);
|
|
20
|
+
if (
|
|
21
|
+
// Check origin because the request might be for a frame; cross-origin frames do not receive activeTab
|
|
22
|
+
possiblyActiveTabs.get(tabId) === origin
|
|
23
|
+
// Don't inject if already registered
|
|
24
|
+
&& !(await isContentScriptRegistered(url))) {
|
|
25
|
+
console.debug('activeTab: will inject', { tabId, frameId, url });
|
|
26
|
+
await injectContentScript({ tabId, frameId }, scripts);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.debug('activeTab: won’t inject', { tabId, frameId, url }, { activeTab: possiblyActiveTabs.get(tabId) ?? 'no' });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function tabListener(tabId, { status }, { url }) {
|
|
33
|
+
// Only status updates are relevant
|
|
34
|
+
// No URL = no permission
|
|
35
|
+
if (status === 'loading' && url) {
|
|
36
|
+
await injectIfActive({ tabId, url, frameId: 0 });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export default function register() {
|
|
40
|
+
addActiveTabListener(injectToTabUnlessRegistered);
|
|
41
|
+
if (gotNavigation) {
|
|
42
|
+
chrome.webNavigation.onCommitted.addListener(injectIfActive);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
chrome.tabs.onUpdated.addListener(tabListener);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type TabId = number;
|
|
2
|
+
type Origin = string;
|
|
3
|
+
export type ActiveTab = {
|
|
4
|
+
id: TabId;
|
|
5
|
+
origin: Origin;
|
|
6
|
+
};
|
|
7
|
+
export declare const possiblyActiveTabs: Map<number, string>;
|
|
8
|
+
export declare function startActiveTabTracking(): void;
|
|
9
|
+
export declare function stopActiveTabTracking(): void;
|
|
10
|
+
export declare function addActiveTabListener(callback: (tab: ActiveTab) => void): void;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { isScriptableUrl } from 'webext-content-scripts';
|
|
2
|
+
import { isBackground } from 'webext-detect';
|
|
3
|
+
import SimpleEventTarget from 'simple-event-target';
|
|
4
|
+
if (!isBackground()) {
|
|
5
|
+
throw new Error('This module is only allowed in a background script');
|
|
6
|
+
}
|
|
7
|
+
const newActiveTabs = new SimpleEventTarget();
|
|
8
|
+
const browserAction = chrome.action ?? chrome.browserAction;
|
|
9
|
+
// TODO: use webext-storage
|
|
10
|
+
export const possiblyActiveTabs = new Map();
|
|
11
|
+
async function addIfScriptable({ url, id }) {
|
|
12
|
+
if (id && url
|
|
13
|
+
// Skip if it already exists. A previous change of origin already cleared this
|
|
14
|
+
&& !possiblyActiveTabs.has(id)
|
|
15
|
+
// ActiveTab doesn't make sense on non-scriptable URLs as they generally don't have scriptable frames
|
|
16
|
+
&& isScriptableUrl(url)
|
|
17
|
+
// Note: Do not filter by `isContentScriptRegistered`; `active-tab` also applies to random `executeScript` calls
|
|
18
|
+
) {
|
|
19
|
+
const { origin } = new URL(url);
|
|
20
|
+
console.debug('activeTab:', id, 'added', { origin });
|
|
21
|
+
possiblyActiveTabs.set(id, origin);
|
|
22
|
+
newActiveTabs.emit({ id, origin });
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.debug('activeTab:', id, 'not added', { origin });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function dropIfOriginChanged(tabId, { url }) {
|
|
29
|
+
if (url && possiblyActiveTabs.has(tabId)) {
|
|
30
|
+
const { origin } = new URL(url);
|
|
31
|
+
if (possiblyActiveTabs.get(tabId) !== origin) {
|
|
32
|
+
console.debug('activeTab:', tabId, 'removed because origin changed from', possiblyActiveTabs.get(tabId), 'to', origin);
|
|
33
|
+
possiblyActiveTabs.delete(tabId);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function altListener(_, tab) {
|
|
38
|
+
if (tab) {
|
|
39
|
+
void addIfScriptable(tab);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function drop(tabId) {
|
|
43
|
+
console.debug('activeTab:', tabId, 'removed');
|
|
44
|
+
possiblyActiveTabs.delete(tabId);
|
|
45
|
+
}
|
|
46
|
+
// https://developer.chrome.com/docs/extensions/mv3/manifest/activeTab/#invoking-activeTab
|
|
47
|
+
export function startActiveTabTracking() {
|
|
48
|
+
browserAction?.onClicked.addListener(addIfScriptable);
|
|
49
|
+
chrome.contextMenus?.onClicked.addListener(altListener);
|
|
50
|
+
chrome.commands?.onCommand.addListener(altListener);
|
|
51
|
+
chrome.tabs.onUpdated.addListener(dropIfOriginChanged);
|
|
52
|
+
chrome.tabs.onRemoved.addListener(drop);
|
|
53
|
+
}
|
|
54
|
+
export function stopActiveTabTracking() {
|
|
55
|
+
browserAction?.onClicked.removeListener(addIfScriptable);
|
|
56
|
+
chrome.contextMenus?.onClicked.removeListener(altListener);
|
|
57
|
+
chrome.commands?.onCommand.removeListener(altListener);
|
|
58
|
+
chrome.tabs.onUpdated.removeListener(dropIfOriginChanged);
|
|
59
|
+
chrome.tabs.onRemoved.removeListener(drop);
|
|
60
|
+
possiblyActiveTabs.clear();
|
|
61
|
+
}
|
|
62
|
+
export function addActiveTabListener(callback) {
|
|
63
|
+
startActiveTabTracking();
|
|
64
|
+
newActiveTabs.listen(callback);
|
|
65
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function isContentScriptStaticallyRegistered(url: string): boolean;
|
|
2
|
+
export declare function isContentScriptDynamicallyRegistered(url: string): Promise<boolean>;
|
|
3
|
+
/**
|
|
4
|
+
Checks whether a URL will have the content scripts automatically injected.
|
|
5
|
+
It returns a promise that resolves with string indicating the type of injection ('static' or 'dynamic') or `false` if it won't be injected on the specified URL.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isContentScriptRegistered(url: string): Promise<'static' | 'dynamic' | false>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { queryAdditionalPermissions } from 'webext-permissions';
|
|
2
|
+
import { patternToRegex } from 'webext-patterns';
|
|
3
|
+
export function isContentScriptStaticallyRegistered(url) {
|
|
4
|
+
return Boolean(chrome.runtime
|
|
5
|
+
.getManifest()
|
|
6
|
+
.content_scripts
|
|
7
|
+
?.flatMap(script => script.matches)
|
|
8
|
+
.some(pattern => patternToRegex(pattern).test(url)));
|
|
9
|
+
}
|
|
10
|
+
export async function isContentScriptDynamicallyRegistered(url) {
|
|
11
|
+
// Injected by `webext-active-tab`
|
|
12
|
+
const { origins } = await queryAdditionalPermissions({
|
|
13
|
+
strictOrigins: false,
|
|
14
|
+
});
|
|
15
|
+
// Do not replace the 2 calls above with `permissions.getAll` because it might also include hosts that are permitted by the manifest but have no content script registered
|
|
16
|
+
return patternToRegex(...origins).test(url);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
Checks whether a URL will have the content scripts automatically injected.
|
|
20
|
+
It returns a promise that resolves with string indicating the type of injection ('static' or 'dynamic') or `false` if it won't be injected on the specified URL.
|
|
21
|
+
*/
|
|
22
|
+
export async function isContentScriptRegistered(url) {
|
|
23
|
+
if (isContentScriptStaticallyRegistered(url)) {
|
|
24
|
+
return 'static';
|
|
25
|
+
}
|
|
26
|
+
if (await isContentScriptDynamicallyRegistered(url)) {
|
|
27
|
+
return 'dynamic';
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
package/license
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Federico Brigante <me@fregante.com> (https://fregante.com)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "webext-active-tab",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "WebExtension module: Automatically registers your `content_scripts` on domains added via `permission.request` or on `activeTab`",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"contentscript",
|
|
7
|
+
"register",
|
|
8
|
+
"injection",
|
|
9
|
+
"permissions",
|
|
10
|
+
"request",
|
|
11
|
+
"optional_permissions",
|
|
12
|
+
"host_permissions",
|
|
13
|
+
"manifest",
|
|
14
|
+
"new hosts",
|
|
15
|
+
"chrome",
|
|
16
|
+
"firefox",
|
|
17
|
+
"browser",
|
|
18
|
+
"activetab",
|
|
19
|
+
"extension"
|
|
20
|
+
],
|
|
21
|
+
"repository": "fregante/webext-active-tab",
|
|
22
|
+
"funding": "https://github.com/sponsors/fregante",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "Federico Brigante <me@fregante.com> (https://fregante.com)",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"exports": {
|
|
27
|
+
"./on-active-tabs": {
|
|
28
|
+
"types": "./distribution/on-active-tabs.d.ts",
|
|
29
|
+
"default": "./distribution/on-active-tabs.js"
|
|
30
|
+
},
|
|
31
|
+
"./register-active-tab-scripts": {
|
|
32
|
+
"types": "./distribution/register-active-tab-scripts.d.ts",
|
|
33
|
+
"default": "./distribution/register-active-tab-scripts.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc",
|
|
38
|
+
"demo:build": "parcel build --no-cache",
|
|
39
|
+
"demo:watch": "parcel watch --no-cache --no-hmr",
|
|
40
|
+
"prepack": "tsc --sourceMap false",
|
|
41
|
+
"lint": "xo",
|
|
42
|
+
"fix": "xo --fix",
|
|
43
|
+
"test": "run-p lint build demo:build",
|
|
44
|
+
"watch": "tsc --watch"
|
|
45
|
+
},
|
|
46
|
+
"xo": {
|
|
47
|
+
"envs": [
|
|
48
|
+
"browser",
|
|
49
|
+
"webextensions"
|
|
50
|
+
],
|
|
51
|
+
"rules": {
|
|
52
|
+
"no-implicit-globals": "off",
|
|
53
|
+
"@typescript-eslint/prefer-nullish-coalescing": "off",
|
|
54
|
+
"@typescript-eslint/no-implicit-any-catch": "off",
|
|
55
|
+
"@typescript-eslint/naming-convention": "off",
|
|
56
|
+
"import/extensions": "off",
|
|
57
|
+
"import/no-unassigned-import": "off",
|
|
58
|
+
"unicorn/prefer-top-level-await": "off",
|
|
59
|
+
"unicorn/prefer-node-protocol": "off",
|
|
60
|
+
"n/file-extension-in-import": "off"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"simple-event-target": "^2.0.0",
|
|
65
|
+
"webext-content-scripts": "^2.7.2",
|
|
66
|
+
"webext-detect": "^5.3.2"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@parcel/config-webextension": "^2.13.3",
|
|
70
|
+
"@sindresorhus/tsconfig": "^7.0.0",
|
|
71
|
+
"@types/chrome": "^0.0.300",
|
|
72
|
+
"npm-run-all2": "^7.0.2",
|
|
73
|
+
"parcel": "^2.13.3",
|
|
74
|
+
"typescript": "^5.7.3",
|
|
75
|
+
"xo": "^0.60.0"
|
|
76
|
+
},
|
|
77
|
+
"@parcel/resolver-default": {
|
|
78
|
+
"packageExports": true
|
|
79
|
+
},
|
|
80
|
+
"targets": {
|
|
81
|
+
"main": false,
|
|
82
|
+
"module": false,
|
|
83
|
+
"demo": {
|
|
84
|
+
"source": "./demo-extension/manifest.json",
|
|
85
|
+
"distDir": "./dist",
|
|
86
|
+
"sourceMap": {
|
|
87
|
+
"inline": true
|
|
88
|
+
},
|
|
89
|
+
"optimize": false
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"webExt": {
|
|
93
|
+
"sourceDir": "dist",
|
|
94
|
+
"run": {
|
|
95
|
+
"startUrl": [
|
|
96
|
+
"https://extra-ephiframe.vercel.app/Inject-via-context-menu-please",
|
|
97
|
+
"https://extra-ephiframe.vercel.app/Inject-via-context-menu-please?iframe=https://ephiframe.vercel.app/Static-inner",
|
|
98
|
+
"chrome://extensions/"
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# webext-active-tab [](https://www.npmjs.com/package/webext-active-tab)
|
|
2
|
+
|
|
3
|
+
> WebExtension module: Track `activeTab` permission; automatically inject content scripts
|
|
4
|
+
|
|
5
|
+
The main use case is to ship your extension without `host_permissions` and have the user invoke the extension on click on any website.
|
|
6
|
+
|
|
7
|
+
Alternatively, consider using [webext-permission-toggle](https://github.com/fregante/webext-permission-toggle) and [webext-dynamic-content-scripts](https://github.com/fregante/webext-dynamic-content-scripts) to permanently receive access to certain hosts and automatically register content scripts on it.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install webext-active-tab
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
This package exports various utilities, just import what you need.
|
|
18
|
+
|
|
19
|
+
- [on-active-tab](./source/on-active-tab.md) - Watch when tabs receive `activeTab`
|
|
20
|
+
- [register-active-tab-scripts](./source/register-active-tab-scripts.md) - Automatically inject scripts into tabs with `activeTab`
|
|
21
|
+
|
|
22
|
+
> [!NOTE]
|
|
23
|
+
> Firefox has some limitations:
|
|
24
|
+
> - `activeTab` seems to be lost after a reload
|
|
25
|
+
> - further `contextMenu` clicks receive a moz-extension URL rather than the current page’s URL
|
|
26
|
+
|
|
27
|
+
## Related
|
|
28
|
+
|
|
29
|
+
- [webext-dynamic-content-scripts](https://github.com/fregante/webext-dynamic-content-scripts) - Automatically registers your `content_scripts` on domains added via `permission.request`.
|
|
30
|
+
- [webext-events](https://github.com/fregante/webext-events) - High-level events and utilities for events in Web Extensions
|
|
31
|
+
- [Awesome-WebExtensions](https://github.com/fregante/Awesome-WebExtensions) - A curated list of awesome resources for WebExtensions development.
|
|
32
|
+
- [More…](https://github.com/fregante/webext-fun)
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
MIT © [Federico Brigante](https://fregante.com)
|