summaryrefslogtreecommitdiff
path: root/source/libs/declarative-content-scripts.ts
blob: 8786253a8edeac461aec07645a262e43e912a0a0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/* global chrome */
import 'content-scripts-register-polyfill';
import 'webext-permissions-events-polyfill';

const registeredScripts = new Map<
string,
Promise<browser.contentScripts.RegisteredContentScript>
>();

// In Firefox, paths in the manifest are converted to full URLs under `moz-extension://` but browser.contentScripts expects exclusively relative paths
function convertPath(file: string): browser.extensionTypes.ExtensionFileOrCode {
	const url = new URL(file, location.origin);
	return {file: url.pathname};
}

async function registerOnOrigins(origins: string[]): Promise<void> {
	const manifest = browser.runtime.getManifest();
	const configs = manifest.content_scripts!;
	const manifestOrigins = [
		...(manifest.permissions || []).filter(permission => permission.includes('://')),
		...configs.flatMap(config => config.matches)
	];

	for (const config of configs) {
		// Register one at a time to allow removing one at a time as well
		for (const origin of origins) {
			// This origin is already part of `manifest.json`
			if (manifestOrigins.includes(origin)) {
				continue;
			}

			const registeredScript = browser.contentScripts.register({
				js: (config.js || []).map(convertPath),
				css: (config.css || []).map(convertPath),
				allFrames: config.all_frames,
				matches: [origin],
				runAt: config.run_at
			});
			registeredScripts.set(origin, registeredScript);
		}
	}
}

// Automatically register the content scripts on the new origins.
// `registerOnOrigins` already takes care of excluding origins in `manifest.json`
chrome.permissions.getAll(async ({origins}) => registerOnOrigins(origins!));

chrome.permissions.onAdded.addListener(({origins}) => {
	if (!origins || origins.length === 0) {
		return;
	}

	registerOnOrigins(origins);
});

chrome.permissions.onRemoved.addListener(async ({origins}) => {
	if (!origins || origins.length === 0) {
		return;
	}

	for (const [origin, script] of registeredScripts) {
		if (origins.includes(origin)) {
			// eslint-disable-next-line no-await-in-loop
			(await script).unregister();
		}
	}
});