eslint-plugin-chrome-extensions 2.4.2 → 2.6.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.
Files changed (47) hide show
  1. package/README.md +272 -115
  2. package/package.json +6 -8
  3. package/src/data/apis.js +166 -115
  4. package/src/data/deprecations.js +5 -7
  5. package/src/data/i18n.js +19 -69
  6. package/src/data/manifest.js +61 -57
  7. package/src/data/permissions.js +23 -30
  8. package/src/data/regex.js +10 -0
  9. package/src/index.js +7 -124
  10. package/src/rules/manifest-key-order.js +8 -8
  11. package/src/rules/manifest-spacing.js +5 -5
  12. package/src/rules/no-content-style-leak.js +6 -4
  13. package/src/rules/no-deprecated-apis.js +4 -4
  14. package/src/rules/no-duplicate-manifest-keys.js +3 -4
  15. package/src/rules/no-insecure-messaging.js +4 -4
  16. package/src/rules/no-invalid-locales.js +89 -0
  17. package/src/rules/no-invalid-permissions.js +10 -13
  18. package/src/rules/no-missing-default-locale.js +44 -21
  19. package/src/rules/no-missing-i18n-keys.js +24 -25
  20. package/src/rules/no-missing-permissions.js +7 -6
  21. package/src/rules/no-missing-web-accessible-resources.js +7 -6
  22. package/src/rules/no-remote-code.js +12 -12
  23. package/src/rules/no-sensitive-permissions.js +75 -0
  24. package/src/rules/no-unrestricted-host-permissions.js +35 -19
  25. package/src/rules/no-unused-i18n-keys.js +11 -10
  26. package/src/rules/no-unused-permissions.js +31 -23
  27. package/src/rules/require-license-file.js +4 -5
  28. package/src/rules/require-mv3.js +5 -6
  29. package/src/rules/require-short-name-when-useful.js +4 -3
  30. package/src/rules/valid-filenames.js +3 -10
  31. package/src/rules/valid-license-file.js +18 -11
  32. package/src/rules/valid-manifest-icons.js +35 -6
  33. package/src/rules/valid-mv3-schema.js +22 -22
  34. package/src/rules/valid-service-worker.js +34 -13
  35. package/src/rules/valid-url-fields.js +49 -41
  36. package/src/rules/valid-version-field.js +5 -9
  37. package/src/utils/extension.js +52 -33
  38. package/src/utils/i18n.js +58 -0
  39. package/src/utils/init.js +107 -0
  40. package/src/utils/license.js +56 -0
  41. package/src/utils/node.js +20 -19
  42. package/src/utils/pkg.js +12 -10
  43. package/src/utils/rules.js +6 -4
  44. package/src/utils/text.js +13 -0
  45. package/src/data/license.js +0 -28
  46. package/src/data/text-parser.js +0 -11
  47. package/src/utils/bundle.js +0 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-chrome-extensions",
3
- "version": "2.4.2",
3
+ "version": "2.6.0",
4
4
  "description": "ESLint rules for Chrome extensions",
5
5
  "keywords": [
6
6
  "eslint",
@@ -54,15 +54,14 @@
54
54
  "bump:minor": "bash utils/bump.sh minor",
55
55
  "bump:patch": "bash utils/bump.sh patch",
56
56
  "demo": "node demo/run.js",
57
- "lint": "eslint . --cache && yamllint .",
58
- "lint:all": "eslint . && yamllint .",
59
- "lint:fix": "eslint . --fix --cache && yamllint .",
60
- "lint:fix-all": "eslint . --fix && yamllint .",
57
+ "lint": "pre-commit run",
58
+ "lint:all": "pre-commit run --all-files",
61
59
  "test": "node tests/run.js"
62
60
  },
63
61
  "dependencies": {
64
62
  "globals": "^17.13.0",
65
- "spdx-license-ids": "^3.0.24"
63
+ "spdx-license-ids": "^3.0.24",
64
+ "spdx-license-list": "^6.12.0"
66
65
  },
67
66
  "devDependencies": {
68
67
  "@e18e/eslint-plugin": "^0.8.1",
@@ -82,8 +81,7 @@
82
81
  "eslint-plugin-package-json": "^1.10.1",
83
82
  "eslint-plugin-promise": "^7.3.0",
84
83
  "eslint-plugin-regexp": "^3.3.1",
85
- "eslint-plugin-security": "^4.2.0",
86
- "yamllint-js": "^0.2.4"
84
+ "eslint-plugin-security": "^4.2.0"
87
85
  },
88
86
  "peerDependencies": {
89
87
  "@eslint/css": ">=0.5.0",
package/src/data/apis.js CHANGED
@@ -1,116 +1,167 @@
1
- export const apiPermissionMap = new Map([
2
- ['chrome.alarms', 'alarms'],
3
- ['chrome.bookmarks', 'bookmarks'],
4
- ['chrome.browsingData', 'browsingData'],
5
- ['chrome.contextMenus', 'contextMenus'],
6
- ['chrome.cookies', 'cookies'],
7
- ['chrome.debugger', 'debugger'],
8
- ['chrome.declarativeNetRequest', 'declarativeNetRequest'],
9
- ['chrome.downloads', 'downloads'],
10
- ['chrome.history', 'history'],
11
- ['chrome.idle', 'idle'],
12
- ['chrome.management', 'management'],
13
- ['chrome.menus', 'menus'],
14
- ['chrome.notifications', 'notifications'],
15
- ['chrome.pageCapture', 'pageCapture'],
16
- ['chrome.privacy', 'privacy'],
17
- ['chrome.proxy', 'proxy'],
18
- ['chrome.scripting', 'scripting'],
19
- ['chrome.sessions', 'sessions'],
20
- ['chrome.storage', 'storage'],
21
- ['chrome.tabCapture', 'tabCapture'],
22
- ['chrome.tabs.query', 'tabs'],
23
- ['chrome.topSites', 'topSites'],
24
- ['chrome.webRequest', 'webRequest']
25
- ])
26
-
27
- export const deprecatedAPIs = new Map([
28
- ['chrome.pageAction.show', 'chrome.action.enable'],
29
- ['chrome.pageAction.hide', 'chrome.action.disable'],
30
- ['chrome.pageAction.setIcon', 'chrome.action.setIcon'],
31
- ['chrome.pageAction.setTitle', 'chrome.action.setTitle'],
32
- ['chrome.pageAction.setPopup', 'chrome.action.setPopup'],
33
- ['chrome.pageAction.getTitle', 'chrome.action.getTitle'],
34
- ['chrome.pageAction.getPopup', 'chrome.action.getPopup'],
35
- ['chrome.browserAction', 'chrome.action'],
36
- ['chrome.pageAction', 'chrome.action'],
37
- ['chrome.extension.getBackgroundPage', 'chrome.runtime.getContexts'],
38
- ['chrome.extension.getURL', 'chrome.runtime.getURL'],
39
- ['chrome.extension.sendRequest', 'chrome.runtime.sendMessage'],
40
- ['chrome.extension.onRequest', 'chrome.runtime.onMessage'],
41
- ['chrome.extension.onRequestExternal', 'chrome.runtime.onMessageExternal'],
42
- ['chrome.extension.connect', 'chrome.runtime.connect'],
43
- ['chrome.extension.onConnect', 'chrome.runtime.onConnect'],
44
- ['chrome.extension.onConnectExternal', 'chrome.runtime.onConnectExternal'],
45
- ['chrome.tabs.executeScript', 'chrome.scripting.executeScript'],
46
- ['chrome.tabs.insertCSS', 'chrome.scripting.insertCSS'],
47
- ['chrome.tabs.removeCSS', 'chrome.scripting.removeCSS']
48
- ])
49
-
50
- export const externalMsgListeners = new Set(['chrome.runtime.onMessageExternal', 'chrome.runtime.onConnectExternal'])
51
-
52
- export const permlessAPIs = new Set([
53
- // https://developer.chrome.com/docs/extensions/reference/api/management
54
- 'chrome.management.getPermissionWarningsByManifest',
55
- 'chrome.management.getSelf',
56
- 'chrome.management.uninstallSelf',
57
- // https://developer.chrome.com/docs/extensions/reference/api/runtime
58
- 'chrome.runtime.connect',
59
- 'chrome.runtime.getManifest',
60
- 'chrome.runtime.getPlatformInfo',
61
- 'chrome.runtime.getURL',
62
- 'chrome.runtime.onBrowserUpdateAvailable', // eslint-disable-line no-secrets/no-secrets
63
- 'chrome.runtime.onConnect',
64
- 'chrome.runtime.onConnectExternal',
65
- 'chrome.runtime.onInstalled',
66
- 'chrome.runtime.onMessage',
67
- 'chrome.runtime.onMessageExternal',
68
- 'chrome.runtime.onStartup',
69
- 'chrome.runtime.onSuspend',
70
- 'chrome.runtime.onSuspendCanceled',
71
- 'chrome.runtime.onUpdateAvailable',
72
- 'chrome.runtime.openOptionsPage',
73
- 'chrome.runtime.reload',
74
- 'chrome.runtime.requestUpdateCheck',
75
- 'chrome.runtime.restart',
76
- 'chrome.runtime.restartAfterDelay',
77
- 'chrome.runtime.sendMessage',
78
- 'chrome.runtime.setUninstallURL',
79
- // https://developer.chrome.com/docs/extensions/reference/api/tabs
80
- 'chrome.tabs.captureVisibleTab',
81
- 'chrome.tabs.connect',
82
- 'chrome.tabs.create',
83
- 'chrome.tabs.detectLanguage',
84
- 'chrome.tabs.discard',
85
- 'chrome.tabs.duplicate',
86
- 'chrome.tabs.executeScript',
87
- 'chrome.tabs.get',
88
- 'chrome.tabs.getCurrent',
89
- 'chrome.tabs.goBack',
90
- 'chrome.tabs.goForward',
91
- 'chrome.tabs.highlight',
92
- 'chrome.tabs.insertCSS',
93
- 'chrome.tabs.move',
94
- 'chrome.tabs.reload',
95
- 'chrome.tabs.remove',
96
- 'chrome.tabs.removeCSS',
97
- 'chrome.tabs.sendMessage',
98
- 'chrome.tabs.update'
99
- ])
100
-
101
- export function findDeprecated(api) {
102
- for (const [deprecated, replacement] of deprecatedAPIs)
103
- if (api == deprecated || api.startsWith(`${deprecated}.`))
104
- return { deprecated, replacement }
105
- return null
1
+ export default {
2
+ deprecated: new Map([
3
+ ['chrome.browserAction', 'chrome.action'],
4
+ ['chrome.declarativeWebRequest', 'chrome.declarativeNetRequest'],
5
+ ['chrome.extension.connect', 'chrome.runtime.connect'],
6
+ ['chrome.extension.getBackgroundPage', 'chrome.runtime.getContexts'],
7
+ ['chrome.extension.getURL', 'chrome.runtime.getURL'],
8
+ ['chrome.extension.lastError', 'chrome.runtime.lastError'],
9
+ ['chrome.extension.onConnect', 'chrome.runtime.onConnect'],
10
+ ['chrome.extension.onConnectExternal', 'chrome.runtime.onConnectExternal'],
11
+ ['chrome.extension.onRequest', 'chrome.runtime.onMessage'],
12
+ ['chrome.extension.onRequestExternal', 'chrome.runtime.onMessageExternal'],
13
+ ['chrome.extension.sendRequest', 'chrome.runtime.sendMessage'],
14
+ ['chrome.fileBrowserHandler', 'File Handling API'],
15
+ ['chrome.pageAction.show', 'chrome.action.enable'],
16
+ ['chrome.pageAction.hide', 'chrome.action.disable'],
17
+ ['chrome.pageAction.setIcon', 'chrome.action.setIcon'],
18
+ ['chrome.pageAction.setTitle', 'chrome.action.setTitle'],
19
+ ['chrome.pageAction.setPopup', 'chrome.action.setPopup'],
20
+ ['chrome.pageAction.getTitle', 'chrome.action.getTitle'],
21
+ ['chrome.pageAction.getPopup', 'chrome.action.getPopup'],
22
+ ['chrome.pageAction', 'chrome.action'], // must stay after pageAction.* — prefix match
23
+ ['chrome.tabs.executeScript', 'chrome.scripting.executeScript'],
24
+ ['chrome.tabs.getAllInWindow', 'chrome.tabs.query({ currentWindow: true })'],
25
+ ['chrome.tabs.getSelected', 'chrome.tabs.query({ active: true })'],
26
+ ['chrome.tabs.insertCSS', 'chrome.scripting.insertCSS'],
27
+ ['chrome.tabs.onActiveChanged', 'chrome.tabs.onActivated'],
28
+ ['chrome.tabs.onSelectionChanged', 'chrome.tabs.onActivated'],
29
+ ['chrome.tabs.removeCSS', 'chrome.scripting.removeCSS'],
30
+ ['chrome.tabs.sendRequest', 'chrome.tabs.sendMessage']
31
+ ]),
32
+ externalMsgListeners: new Set(['chrome.runtime.onMessageExternal', 'chrome.runtime.onConnectExternal']),
33
+ permissionMap: new Map([
34
+ ['chrome.accessibilityFeatures.set', 'accessibilityFeatures.modify'],
35
+ ['chrome.accessibilityFeatures.clear', 'accessibilityFeatures.modify'],
36
+ ['chrome.accessibilityFeatures', 'accessibilityFeatures.read'],
37
+ ['chrome.alarms', 'alarms'],
38
+ ['chrome.audio', 'audio'],
39
+ ['chrome.bookmarks', 'bookmarks'],
40
+ ['chrome.browsingData', 'browsingData'],
41
+ ['chrome.certificateProvider', 'certificateProvider'],
42
+ ['chrome.contentSettings', 'contentSettings'],
43
+ ['chrome.contextMenus', 'contextMenus'],
44
+ ['chrome.cookies', 'cookies'],
45
+ ['chrome.debugger', 'debugger'],
46
+ ['chrome.declarativeContent', 'declarativeContent'],
47
+ ['chrome.declarativeNetRequest', 'declarativeNetRequest'],
48
+ ['chrome.dns', 'dns'],
49
+ ['chrome.desktopCapture', 'desktopCapture'],
50
+ ['chrome.documentScan', 'documentScan'],
51
+ ['chrome.downloads.open', 'downloads.open'],
52
+ ['chrome.downloads.setUiOptions', 'downloads.ui'],
53
+ ['chrome.downloads', 'downloads'],
54
+ ['chrome.enterprise.deviceAttributes', 'enterprise.deviceAttributes'],
55
+ ['chrome.enterprise.hardwarePlatform', 'enterprise.hardwarePlatform'],
56
+ ['chrome.enterprise.networkingAttributes','enterprise.networkingAttributes'],
57
+ ['chrome.enterprise.platformKeys', 'enterprise.platformKeys'],
58
+ ['chrome.favicon', 'favicon'],
59
+ ['chrome.fileBrowserHandler', 'fileBrowserHandler'],
60
+ ['chrome.fontSettings', 'fontSettings'],
61
+ ['chrome.gcm', 'gcm'],
62
+ ['chrome.instanceID', 'gcm'],
63
+ ['chrome.history', 'history'],
64
+ ['chrome.identity.getProfileUserInfo', 'identity.email'],
65
+ ['chrome.identity', 'identity'],
66
+ ['chrome.idle', 'idle'],
67
+ ['chrome.loginState', 'loginState'],
68
+ ['chrome.management', 'management'],
69
+ ['chrome.runtime.connectNative', 'nativeMessaging'],
70
+ ['chrome.runtime.sendNativeMessage', 'nativeMessaging'],
71
+ ['chrome.notifications', 'notifications'],
72
+ ['chrome.offscreen', 'offscreen'],
73
+ ['chrome.pageCapture', 'pageCapture'],
74
+ ['chrome.platformKeys', 'platformKeys'],
75
+ ['chrome.power', 'power'],
76
+ ['chrome.printerProvider', 'printerProvider'],
77
+ ['chrome.printingMetrics', 'printingMetrics'],
78
+ ['chrome.printing', 'printing'],
79
+ ['chrome.privacy', 'privacy'],
80
+ ['chrome.processes', 'processes'],
81
+ ['chrome.proxy', 'proxy'],
82
+ ['chrome.publicSuffix', 'publicSuffix'],
83
+ ['chrome.readingList', 'readingList'],
84
+ ['chrome.scripting', 'scripting'],
85
+ ['chrome.search', 'search'],
86
+ ['chrome.sessions', 'sessions'],
87
+ ['chrome.sidePanel', 'sidePanel'],
88
+ ['chrome.storage', 'storage'],
89
+ ['chrome.system.cpu', 'system.cpu'],
90
+ ['chrome.system.display', 'system.display'],
91
+ ['chrome.system.memory', 'system.memory'],
92
+ ['chrome.system.storage', 'system.storage'],
93
+ ['chrome.tabCapture', 'tabCapture'],
94
+ ['chrome.tabGroups', 'tabGroups'],
95
+ ['chrome.tabs.query', 'tabs'],
96
+ ['chrome.topSites', 'topSites'],
97
+ ['chrome.ttsEngine', 'ttsEngine'],
98
+ ['chrome.tts', 'tts'],
99
+ ['chrome.userScripts', 'userScripts'],
100
+ ['chrome.vpnProvider', 'vpnProvider'],
101
+ ['chrome.wallpaper', 'wallpaper'],
102
+ ['chrome.webAuthenticationProxy', 'webAuthenticationProxy'],
103
+ ['chrome.webNavigation', 'webNavigation'],
104
+ ['chrome.webRequest', 'webRequest']
105
+ ]),
106
+ permless: new Set([
107
+ 'chrome.i18n',
108
+ 'chrome.management.getPermissionWarningsByManifest',
109
+ 'chrome.management.getSelf',
110
+ 'chrome.management.uninstallSelf',
111
+ 'chrome.runtime.connect',
112
+ 'chrome.runtime.getManifest',
113
+ 'chrome.runtime.getPlatformInfo',
114
+ 'chrome.runtime.getURL',
115
+ 'chrome.runtime.onBrowserUpdateAvailable', // eslint-disable-line no-secrets/no-secrets
116
+ 'chrome.runtime.onConnect',
117
+ 'chrome.runtime.onConnectExternal',
118
+ 'chrome.runtime.onInstalled',
119
+ 'chrome.runtime.onMessage',
120
+ 'chrome.runtime.onMessageExternal',
121
+ 'chrome.runtime.onStartup',
122
+ 'chrome.runtime.onSuspend',
123
+ 'chrome.runtime.onSuspendCanceled',
124
+ 'chrome.runtime.onUpdateAvailable',
125
+ 'chrome.runtime.openOptionsPage',
126
+ 'chrome.runtime.reload',
127
+ 'chrome.runtime.requestUpdateCheck',
128
+ 'chrome.runtime.restart',
129
+ 'chrome.runtime.restartAfterDelay',
130
+ 'chrome.runtime.sendMessage',
131
+ 'chrome.runtime.setUninstallURL',
132
+ 'chrome.tabs.captureVisibleTab',
133
+ 'chrome.tabs.connect',
134
+ 'chrome.tabs.create',
135
+ 'chrome.tabs.detectLanguage',
136
+ 'chrome.tabs.discard',
137
+ 'chrome.tabs.duplicate',
138
+ 'chrome.tabs.executeScript',
139
+ 'chrome.tabs.get',
140
+ 'chrome.tabs.getCurrent',
141
+ 'chrome.tabs.goBack',
142
+ 'chrome.tabs.goForward',
143
+ 'chrome.tabs.highlight',
144
+ 'chrome.tabs.insertCSS',
145
+ 'chrome.tabs.move',
146
+ 'chrome.tabs.reload',
147
+ 'chrome.tabs.remove',
148
+ 'chrome.tabs.removeCSS',
149
+ 'chrome.tabs.sendMessage',
150
+ 'chrome.tabs.update',
151
+ 'chrome.windows'
152
+ ]),
153
+ findDeprecated(api) {
154
+ for (const [deprecated, replacement] of this.deprecated)
155
+ if (api == deprecated || api.startsWith(`${deprecated}.`))
156
+ return { deprecated, replacement }
157
+ return null
158
+ },
159
+ findPermission(api) {
160
+ if (this.permless.has(api)) return null
161
+ for (const [prefix, permission] of this.permissionMap)
162
+ if (api == prefix || api.startsWith(`${prefix}.`))
163
+ return permission
164
+ return null
165
+ },
166
+ isExternalMsgListener(api) { return this.externalMsgListeners.has(api) }
106
167
  }
107
-
108
- export function findPermission(api) {
109
- if (permlessAPIs.has(api)) return null
110
- for (const [prefix, permission] of apiPermissionMap)
111
- if (api == prefix || api.startsWith(`${prefix}.`))
112
- return permission
113
- return null
114
- }
115
-
116
- export function isExternalMessagingListener(api) { return externalMsgListeners.has(api) }
@@ -1,10 +1,8 @@
1
1
  // Delete a line to end support
2
2
 
3
- export const bundles = {
4
- 'no-invalid-permissions': ['recommended'], // 2026-10-5 announced future removal
5
- 'require-mv3': ['recommended'], // 2026-10-5 announced future removal
6
- 'valid-mv3-schema': ['recommended'], // 2026-10-5 announced future removal
7
- 'valid-version-field': ['recommended'] // 2026-10-5 announced future removal
3
+ export default {
4
+ bundles: {
5
+ // <rule-id-a>: [<old-bundle>],
6
+ // <rule-id-b>: [<old-bundle>]
7
+ }
8
8
  }
9
-
10
- export default { bundles }
package/src/data/i18n.js CHANGED
@@ -1,71 +1,21 @@
1
- /* eslint-disable security/detect-non-literal-fs-filename -- lint-time project inspection only */
2
-
3
- import fs from 'node:fs'
4
- import path from 'node:path'
5
-
6
- export const re = {
7
- getMsgLiteral: /\b(?:chrome|browser)\.i18n\.getMessage\(\s*['"]([^'"]+)['"]/g,
8
- getMsgTemplate: /\b(?:chrome|browser)\.i18n\.getMessage\(\s*`([^`]*)`/g,
9
- msgPlaceholder: /__MSG_([\w@]+)__/g,
10
- specialChars: /[.*+?^${}()|[\]\\]/g,
11
- templateExpr: /\$\{[^}]*\}/g
12
- }
13
-
14
- export function collectUsedKeys(root, scanExtensions) {
15
- const used = new Set(),
16
- patterns = [],
17
- exts = new Set(scanExtensions),
18
- stack = [root]
19
- while (stack.length) {
20
- const dir = stack.pop()
21
- let entries
22
- try { entries = fs.readdirSync(dir, { withFileTypes: true }) } catch { continue }
23
- for (const entry of entries) {
24
- if (entry.name.startsWith('.') || entry.name == 'node_modules') continue
25
- const full = path.join(dir, entry.name)
26
- if (entry.isDirectory()) { stack.push(full) ; continue }
27
- const ext = path.extname(entry.name).slice(1).toLowerCase()
28
- if (!exts.has(ext)) continue
29
- let text
30
- try { text = fs.readFileSync(full, 'utf8') } catch { continue }
31
- for (const match of text.matchAll(re.msgPlaceholder))
32
- if (!isPredefined(match[1])) used.add(match[1])
33
- for (const match of text.matchAll(re.getMsgLiteral))
34
- used.add(match[1])
35
- for (const match of text.matchAll(re.getMsgTemplate)) {
36
- const body = match[1]
37
- if (body.includes('${')) patterns.push(templateToPattern(body))
38
- else used.add(body)
39
- }
40
- }
41
- }
42
- return { used, patterns }
43
- }
44
-
45
- export const defaultScanExtensions = [
46
- 'cjs', 'css', 'htm', 'html', 'js', 'json', 'jsx', 'md', 'mjs', 'svg', 'ts', 'tsx', 'txt']
47
-
48
- export function findExtensionRoot(startPath) {
49
- let dir = path.dirname(startPath)
50
- while (dir != path.dirname(dir)) {
51
- if (fs.existsSync(path.join(dir, 'manifest.json'))) return dir
52
- dir = path.dirname(dir)
1
+ export default {
2
+ defaultScanExtensions: ['cjs', 'css', 'htm', 'html', 'js', 'json', 'jsx', 'md', 'mjs', 'svg', 'ts', 'tsx', 'txt'],
3
+ locales: {
4
+ supported: new Set([ // https://developer.chrome.com/docs/extensions/reference/api/i18n
5
+ 'af', 'am', 'ar', 'bg', 'bn', 'ca', 'cs', 'da', 'de', 'el', 'en', 'en_AU', 'en_GB', 'en_IN', 'en_US', 'es',
6
+ 'es_419', 'es_MX', 'es_US', 'et', 'fa', 'fi', 'fil', 'fr', 'fr_CA', 'fr_FR', 'gu', 'he', 'hi', 'hr', 'hu',
7
+ 'id', 'it', 'ja', 'kn', 'ko', 'lt', 'lv', 'ml', 'mr', 'ms', 'nl', 'no', 'pl', 'pt_BR', 'pt_PT', 'ro', 'ru',
8
+ 'sk', 'sl', 'sr', 'sv', 'sw', 'ta', 'te', 'th', 'tr', 'uk', 'vi', 'zh_CN', 'zh_TW'
9
+ ])
10
+ },
11
+ re: {
12
+ getMsg: {
13
+ literal: /\b(?:browser|chrome)\.i18n\.getMessage\(\s*['"]([^'"]+)['"]/g,
14
+ path: /^(?:browser|chrome)\.i18n\.getMessage$/,
15
+ template: /\b(?:browser|chrome)\.i18n\.getMessage\(\s*`([^`]*)`/g
16
+ },
17
+ msgPlaceholder: /__MSG_([\w@]+)__/g,
18
+ specialChars: /[.*+?^${}()|[\]\\]/g,
19
+ templateExpr: /\$\{[^}]*\}/g
53
20
  }
54
21
  }
55
-
56
- export function isPredefined(key) { return key.startsWith('@@') }
57
-
58
- export function readDefaultLocaleMessages(manifest, root) {
59
- const locale = manifest?.default_locale
60
- if (typeof locale != 'string') return null
61
- const filePath = path.join(root, '_locales', locale, 'messages.json')
62
- let messages
63
- try { messages = JSON.parse(fs.readFileSync(filePath, 'utf8')) } catch { return null }
64
- if (!messages || typeof messages != 'object' || Array.isArray(messages)) return null
65
- return { locale, messages }
66
- }
67
-
68
- export function templateToPattern(body) {
69
- const parts = body.split(re.templateExpr).map(part => part.replace(re.specialChars, '\\$&'))
70
- return new RegExp(`^${parts.join('.*')}$`) // eslint-disable-line security/detect-non-literal-regexp
71
- }
@@ -1,61 +1,65 @@
1
1
  import globals from 'globals'
2
2
 
3
3
  const serviceWorkerGlobals = new Set(Object.keys(globals.serviceworker || {}))
4
- export const domGlobals = new Set(Object.keys(globals.browser).filter(name => !serviceWorkerGlobals.has(name)))
5
4
 
6
- export const ffDataCollectionRequired = new Set([
7
- 'none', 'authenticationInfo', 'bookmarksInfo', 'browsingActivity', 'financialAndPaymentInfo', 'healthInfo',
8
- 'locationInfo', 'personalCommunications', 'personallyIdentifyingInfo', 'searchTerms', 'websiteActivity',
9
- 'websiteContent'
10
- ])
11
-
12
- export const ffDataCollectionOptional = new Set([
13
- 'authenticationInfo', 'bookmarksInfo', 'browsingActivity', 'financialAndPaymentInfo', 'healthInfo', 'locationInfo',
14
- 'personalCommunications', 'personallyIdentifyingInfo', 'searchTerms', 'technicalAndInteraction', 'websiteActivity',
15
- 'websiteContent'
16
- ])
17
-
18
- export const ffMV3bgKeys = new Set(['scripts', 'page'])
19
-
20
- export const forbiddenCSPdirectives = [
21
- `'unsafe-eval'`, `'unsafe-inline'`, `'unsafe-hashes'`, `'wasm-unsafe-eval'`, 'http://', 'https://']
22
-
23
- export const manifestKeyOrder = [
24
- // platform-required
25
- 'manifest_version', 'name', 'version',
26
- // CWS-required
27
- 'description', 'icons',
28
- // optional
29
- 'action', 'author', 'background', 'browser_specific_settings', 'chrome_settings_overrides', 'chrome_url_overrides',
30
- 'commands', 'content_scripts', 'content_security_policy', 'cross_origin_embedder_policy',
31
- 'cross_origin_opener_policy', 'dark_theme', 'declarative_net_request', 'default_locale', 'developer',
32
- 'devtools_page', 'dictionaries', 'export', 'externally_connectable', 'homepage_url', 'host_permissions', 'import',
33
- 'incognito', 'key', 'mime_types_handler', 'minimum_chrome_version', 'oauth2', 'omnibox',
34
- 'optional_host_permissions', 'optional_permissions', 'options_page', 'options_ui', 'permissions',
35
- 'protocol_handlers', 'requirements', 'sandbox', 'short_name', 'sidebar_action', 'side_panel', 'storage', 'theme',
36
- 'theme_experiment', 'tts_engine', 'update_url', 'version_name', 'web_accessible_resources'
37
- ]
38
-
39
- export const matchPaths = new Set([
40
- 'content_scripts.matches', 'externally_connectable.matches',
41
- 'host_permissions', 'optional_host_permissions', 'web_accessible_resources.matches'
42
- ])
43
-
44
- export const mv2OnlyKeys = new Set(['browser_action', 'page_action'])
45
-
46
- export const remoteCodePatterns = [
47
- 'eval', 'new-function', 'string-timer', 'dynamic-import', 'remote-script-src', 'execute-script-url']
48
-
49
- export const serviceWorkerKeepaliveMinMS = 30000
50
-
51
- export const urlPaths = new Set([
52
- 'homepage_url', 'update_url', 'oauth2.scopes',
53
- 'chrome_settings_overrides.homepage',
54
- 'chrome_settings_overrides.search_provider.alternate_urls',
55
- 'chrome_settings_overrides.search_provider.favicon_url',
56
- 'chrome_settings_overrides.search_provider.image_url',
57
- 'chrome_settings_overrides.search_provider.instant_url',
58
- 'chrome_settings_overrides.search_provider.search_url',
59
- 'chrome_settings_overrides.search_provider.suggest_url',
60
- 'chrome_settings_overrides.startup_pages'
61
- ])
5
+ export default {
6
+ csp: {
7
+ forbiddenDirectives: [
8
+ `'unsafe-eval'`, `'unsafe-inline'`, `'unsafe-hashes'`, `'wasm-unsafe-eval'`, 'http://', 'https://']
9
+ },
10
+ globals: {
11
+ dom: new Set(Object.keys(globals.browser).filter(name => !serviceWorkerGlobals.has(name))) },
12
+ keys: {
13
+ ff: {
14
+ dataCollectionOptional: new Set([
15
+ 'authenticationInfo', 'bookmarksInfo', 'browsingActivity', 'financialAndPaymentInfo',
16
+ 'healthInfo', 'locationInfo', 'personalCommunications', 'personallyIdentifyingInfo',
17
+ 'searchTerms', 'technicalAndInteraction', 'websiteActivity', 'websiteContent'
18
+ ]),
19
+ dataCollectionRequired: new Set([
20
+ 'none', 'authenticationInfo', 'bookmarksInfo', 'browsingActivity', 'financialAndPaymentInfo',
21
+ 'healthInfo', 'locationInfo', 'personalCommunications', 'personallyIdentifyingInfo', 'searchTerms',
22
+ 'websiteActivity', 'websiteContent'
23
+ ]),
24
+ mv3BG: new Set(['scripts', 'page'])
25
+ },
26
+ mv2Only: new Set(['browser_action', 'page_action']),
27
+ order: [
28
+ // platform-required
29
+ 'manifest_version', 'name', 'version',
30
+ // CWS-required
31
+ 'description', 'icons',
32
+ // optional
33
+ 'action', 'author', 'background', 'browser_specific_settings', 'chrome_settings_overrides',
34
+ 'chrome_url_overrides', 'commands', 'content_scripts', 'content_security_policy',
35
+ 'cross_origin_embedder_policy', 'cross_origin_opener_policy', 'dark_theme', 'declarative_net_request',
36
+ 'default_locale', 'developer', 'devtools_page', 'dictionaries', 'export', 'externally_connectable',
37
+ 'homepage_url', 'host_permissions', 'import', 'incognito', 'key', 'mime_types_handler',
38
+ 'minimum_chrome_version', 'oauth2', 'omnibox', 'optional_host_permissions', 'optional_permissions',
39
+ 'options_page', 'options_ui', 'permissions', 'protocol_handlers', 'requirements', 'sandbox',
40
+ 'short_name', 'sidebar_action', 'side_panel', 'storage', 'theme', 'theme_experiment', 'tts_engine',
41
+ 'update_url', 'version_name', 'web_accessible_resources'
42
+ ]
43
+ },
44
+ paths: {
45
+ match: new Set([
46
+ 'content_scripts.matches', 'externally_connectable.matches', 'host_permissions',
47
+ 'optional_host_permissions', 'web_accessible_resources.matches'
48
+ ]),
49
+ url: new Set([
50
+ 'homepage_url', 'update_url', 'oauth2.scopes',
51
+ 'chrome_settings_overrides.homepage',
52
+ 'chrome_settings_overrides.search_provider.alternate_urls',
53
+ 'chrome_settings_overrides.search_provider.favicon_url',
54
+ 'chrome_settings_overrides.search_provider.image_url',
55
+ 'chrome_settings_overrides.search_provider.instant_url',
56
+ 'chrome_settings_overrides.search_provider.search_url',
57
+ 'chrome_settings_overrides.search_provider.suggest_url',
58
+ 'chrome_settings_overrides.startup_pages'
59
+ ])
60
+ },
61
+ patterns: {
62
+ remoteCode: [
63
+ 'eval', 'new-function', 'string-timer', 'dynamic-import', 'remote-script-src', 'execute-script-url']
64
+ }
65
+ }