|
1 |
| -/* https://github.com/fregante/webext-domain-permission-toggle @ v1.0.1 */ |
| 1 | +/* https://github.com/fregante/webext-domain-permission-toggle @ v2.1.0 */ |
2 | 2 |
|
3 | 3 | var addDomainPermissionToggle = (function () {
|
4 |
| - 'use strict'; |
| 4 | + 'use strict'; |
5 | 5 |
|
6 |
| - async function getManifestPermissions() { |
7 |
| - return getManifestPermissionsSync(); |
8 |
| - } |
9 |
| - function getManifestPermissionsSync() { |
10 |
| - var _a, _b; |
11 |
| - const manifest = chrome.runtime.getManifest(); |
12 |
| - const manifestPermissions = { |
13 |
| - origins: [], |
14 |
| - permissions: [] |
15 |
| - }; |
16 |
| - const list = new Set([ |
17 |
| - ...((_a = manifest.permissions) !== null && _a !== void 0 ? _a : []), |
18 |
| - ...((_b = manifest.content_scripts) !== null && _b !== void 0 ? _b : []).flatMap(config => { var _a; return (_a = config.matches) !== null && _a !== void 0 ? _a : []; }) |
19 |
| - ]); |
20 |
| - for (const permission of list) { |
21 |
| - if (permission.includes('://')) { |
22 |
| - manifestPermissions.origins.push(permission); |
23 |
| - } |
24 |
| - else { |
25 |
| - manifestPermissions.permissions.push(permission); |
26 |
| - } |
27 |
| - } |
28 |
| - return manifestPermissions; |
29 |
| - } |
| 6 | + function NestedProxy(target) { |
| 7 | + return new Proxy(target, { |
| 8 | + get(target, prop) { |
| 9 | + if (typeof target[prop] !== 'function') { |
| 10 | + return new NestedProxy(target[prop]); |
| 11 | + } |
| 12 | + return (...arguments_) => |
| 13 | + new Promise((resolve, reject) => { |
| 14 | + target[prop](...arguments_, result => { |
| 15 | + if (chrome.runtime.lastError) { |
| 16 | + reject(new Error(chrome.runtime.lastError.message)); |
| 17 | + } else { |
| 18 | + resolve(result); |
| 19 | + } |
| 20 | + }); |
| 21 | + }); |
| 22 | + } |
| 23 | + }); |
| 24 | + } |
| 25 | + const chromeP = |
| 26 | + typeof window === 'object' && |
| 27 | + (window.browser || new NestedProxy(window.chrome)); |
30 | 28 |
|
31 |
| - const contextMenuId = 'webext-domain-permission-toggle:add-permission'; |
32 |
| - let currentTabId; |
33 |
| - let globalOptions; |
34 |
| - async function p(fn, ...args) { |
35 |
| - return new Promise((resolve, reject) => { |
36 |
| - fn(...args, result => { |
37 |
| - if (chrome.runtime.lastError) { |
38 |
| - reject(chrome.runtime.lastError); |
39 |
| - } |
40 |
| - else { |
41 |
| - resolve(result); |
42 |
| - } |
43 |
| - }); |
44 |
| - }); |
45 |
| - } |
46 |
| - async function isOriginPermanentlyAllowed(origin) { |
47 |
| - return p(chrome.permissions.contains, { |
48 |
| - origins: [ |
49 |
| - origin + '/*' |
50 |
| - ] |
51 |
| - }); |
52 |
| - } |
53 |
| - function createMenu() { |
54 |
| - chrome.contextMenus.remove(contextMenuId, () => chrome.runtime.lastError); |
55 |
| - chrome.contextMenus.create({ |
56 |
| - id: contextMenuId, |
57 |
| - type: 'checkbox', |
58 |
| - checked: false, |
59 |
| - title: globalOptions.title, |
60 |
| - contexts: [ |
61 |
| - 'page_action', |
62 |
| - 'browser_action' |
63 |
| - ], |
64 |
| - documentUrlPatterns: [ |
65 |
| - 'http://*/*', |
66 |
| - 'https://*/*' |
67 |
| - ] |
68 |
| - }); |
69 |
| - } |
70 |
| - function updateItem({ tabId }) { |
71 |
| - chrome.tabs.executeScript(tabId, { |
72 |
| - code: 'location.origin' |
73 |
| - }, async ([origin] = []) => { |
74 |
| - const settings = { |
75 |
| - checked: false, |
76 |
| - enabled: true |
77 |
| - }; |
78 |
| - if (!chrome.runtime.lastError && origin) { |
79 |
| - const manifestPermissions = await getManifestPermissions(); |
80 |
| - const isDefault = manifestPermissions.origins.some(permission => permission.startsWith(origin)); |
81 |
| - settings.enabled = !isDefault; |
82 |
| - settings.checked = isDefault || await isOriginPermanentlyAllowed(origin); |
83 |
| - } |
84 |
| - chrome.contextMenus.update(contextMenuId, settings); |
85 |
| - }); |
86 |
| - } |
87 |
| - async function handleClick({ wasChecked, menuItemId }, tab) { |
88 |
| - if (menuItemId !== contextMenuId || !tab) { |
89 |
| - return; |
90 |
| - } |
91 |
| - try { |
92 |
| - const successful = await p(wasChecked ? chrome.permissions.remove : chrome.permissions.request, { |
93 |
| - origins: [ |
94 |
| - new URL(tab.url).origin + '/*' |
95 |
| - ] |
96 |
| - }); |
97 |
| - if (wasChecked && successful) { |
98 |
| - chrome.contextMenus.update(contextMenuId, { |
99 |
| - checked: false |
100 |
| - }); |
101 |
| - } |
102 |
| - if (!wasChecked && successful && globalOptions.reloadOnSuccess) { |
103 |
| - chrome.tabs.executeScript({ |
104 |
| - code: `confirm(${JSON.stringify(globalOptions.reloadOnSuccess)}) && location.reload()` |
105 |
| - }); |
106 |
| - } |
107 |
| - } |
108 |
| - catch (error) { |
109 |
| - console.error(error.message); |
110 |
| - alert(`Error: ${error.message}`); |
111 |
| - updateItem({ tabId: tab.id }); |
112 |
| - } |
113 |
| - } |
114 |
| - function addDomainPermissionToggle(options) { |
115 |
| - if (globalOptions) { |
116 |
| - throw new Error('webext-domain-permission-toggle can only be initialized once'); |
117 |
| - } |
118 |
| - const { name } = chrome.runtime.getManifest(); |
119 |
| - globalOptions = { title: `Enable ${name} on this domain`, |
120 |
| - reloadOnSuccess: `Do you want to reload this page to apply ${name}?`, ...options }; |
121 |
| - chrome.contextMenus.onClicked.addListener(handleClick); |
122 |
| - chrome.tabs.onActivated.addListener(updateItem); |
123 |
| - chrome.tabs.onUpdated.addListener((tabId, { status }) => { |
124 |
| - if (currentTabId === tabId && status === 'complete') { |
125 |
| - updateItem({ tabId }); |
126 |
| - } |
127 |
| - }); |
128 |
| - createMenu(); |
129 |
| - } |
| 29 | + const patternValidationRegex = /^(https?|wss?|file|ftp|\*):\/\/(\*|\*\.[^*/]+|[^*/]+)\/.*$|^file:\/\/\/.*$|^resource:\/\/(\*|\*\.[^*/]+|[^*/]+)\/.*$|^about:/; |
| 30 | + const isFirefox = typeof navigator === 'object' && navigator.userAgent.includes('Firefox/'); |
| 31 | + function getRawRegex(matchPattern) { |
| 32 | + if (!patternValidationRegex.test(matchPattern)) { |
| 33 | + throw new Error(matchPattern + ' is an invalid pattern, it must match ' + String(patternValidationRegex)); |
| 34 | + } |
| 35 | + let [, protocol, host, pathname] = matchPattern.split(/(^[^:]+:[/][/])([^/]+)?/); |
| 36 | + protocol = protocol |
| 37 | + .replace('*', isFirefox ? '(https?|wss?)' : 'https?') |
| 38 | + .replace(/[/]/g, '[/]'); |
| 39 | + host = (host !== null && host !== void 0 ? host : '') |
| 40 | + .replace(/^[*][.]/, '([^/]+.)*') |
| 41 | + .replace(/^[*]$/, '[^/]+') |
| 42 | + .replace(/[.]/g, '[.]') |
| 43 | + .replace(/[*]$/g, '[^.]+'); |
| 44 | + pathname = pathname |
| 45 | + .replace(/[/]/g, '[/]') |
| 46 | + .replace(/[.]/g, '[.]') |
| 47 | + .replace(/[*]/g, '.*'); |
| 48 | + return '^' + protocol + host + '(' + pathname + ')?$'; |
| 49 | + } |
| 50 | + function patternToRegex(...matchPatterns) { |
| 51 | + if (matchPatterns.includes('<all_urls>')) { |
| 52 | + return /^(https?|file|ftp):[/]+/; |
| 53 | + } |
| 54 | + return new RegExp(matchPatterns.map(getRawRegex).join('|')); |
| 55 | + } |
130 | 56 |
|
131 |
| - return addDomainPermissionToggle; |
| 57 | + const isExtensionContext = typeof chrome === 'object' && chrome && typeof chrome.extension === 'object'; |
| 58 | + const globalWindow = typeof window === 'object' ? window : undefined; |
| 59 | + const isWeb = typeof location === 'object' && location.protocol.startsWith('http'); |
| 60 | + function isBackgroundPage() { |
| 61 | + var _a, _b; |
| 62 | + return isExtensionContext && (location.pathname === '/_generated_background_page.html' || |
| 63 | + ((_b = (_a = chrome.extension) === null || _a === void 0 ? void 0 : _a.getBackgroundPage) === null || _b === void 0 ? void 0 : _b.call(_a)) === globalWindow); |
| 64 | + } |
| 65 | + |
| 66 | + function getManifestPermissionsSync() { |
| 67 | + return _getManifestPermissionsSync(chrome.runtime.getManifest()); |
| 68 | + } |
| 69 | + function _getManifestPermissionsSync(manifest) { |
| 70 | + var _a, _b; |
| 71 | + const manifestPermissions = { |
| 72 | + origins: [], |
| 73 | + permissions: [] |
| 74 | + }; |
| 75 | + const list = new Set([ |
| 76 | + ...((_a = manifest.permissions) !== null && _a !== void 0 ? _a : []), |
| 77 | + ...((_b = manifest.content_scripts) !== null && _b !== void 0 ? _b : []).flatMap(config => { var _a; return (_a = config.matches) !== null && _a !== void 0 ? _a : []; }) |
| 78 | + ]); |
| 79 | + for (const permission of list) { |
| 80 | + if (permission.includes('://')) { |
| 81 | + manifestPermissions.origins.push(permission); |
| 82 | + } |
| 83 | + else { |
| 84 | + manifestPermissions.permissions.push(permission); |
| 85 | + } |
| 86 | + } |
| 87 | + return manifestPermissions; |
| 88 | + } |
| 89 | + |
| 90 | + const isFirefox$1 = typeof navigator === 'object' && navigator.userAgent.includes('Firefox/'); |
| 91 | + const contextMenuId = 'webext-domain-permission-toggle:add-permission'; |
| 92 | + let globalOptions; |
| 93 | + async function executeCode(tabId, function_, ...args) { |
| 94 | + return chromeP.tabs.executeScript(tabId, { |
| 95 | + code: `(${function_.toString()})(...${JSON.stringify(args)})` |
| 96 | + }); |
| 97 | + } |
| 98 | + async function isOriginPermanentlyAllowed(origin) { |
| 99 | + return chromeP.permissions.contains({ |
| 100 | + origins: [origin + '/*'] |
| 101 | + }); |
| 102 | + } |
| 103 | + async function getTabUrl(tabId) { |
| 104 | + if (isFirefox$1) { |
| 105 | + const [url] = await executeCode(tabId, () => location.href); |
| 106 | + return url; |
| 107 | + } |
| 108 | + const tab = await chromeP.tabs.get(tabId); |
| 109 | + return tab.url; |
| 110 | + } |
| 111 | + async function updateItem(url) { |
| 112 | + const settings = { |
| 113 | + checked: false, |
| 114 | + enabled: true |
| 115 | + }; |
| 116 | + if (url) { |
| 117 | + const origin = new URL(url).origin; |
| 118 | + const manifestPermissions = getManifestPermissionsSync(); |
| 119 | + const isDefault = patternToRegex(...manifestPermissions.origins).test(origin); |
| 120 | + settings.enabled = !isDefault; |
| 121 | + settings.checked = isDefault || await isOriginPermanentlyAllowed(origin); |
| 122 | + } |
| 123 | + chrome.contextMenus.update(contextMenuId, settings); |
| 124 | + } |
| 125 | + async function togglePermission(tab, toggle) { |
| 126 | + const safariError = 'The browser didn\'t supply any information about the active tab.'; |
| 127 | + if (!tab.url && toggle) { |
| 128 | + throw new Error(`Please try again. ${safariError}`); |
| 129 | + } |
| 130 | + if (!tab.url && !toggle) { |
| 131 | + throw new Error(`Couldn't disable the extension on the current tab. ${safariError}`); |
| 132 | + } |
| 133 | + const permissionData = { |
| 134 | + origins: [ |
| 135 | + new URL(tab.url).origin + '/*' |
| 136 | + ] |
| 137 | + }; |
| 138 | + if (!toggle) { |
| 139 | + void chromeP.permissions.remove(permissionData); |
| 140 | + return; |
| 141 | + } |
| 142 | + const userAccepted = await chromeP.permissions.request(permissionData); |
| 143 | + if (!userAccepted) { |
| 144 | + chrome.contextMenus.update(contextMenuId, { |
| 145 | + checked: false |
| 146 | + }); |
| 147 | + return; |
| 148 | + } |
| 149 | + if (globalOptions.reloadOnSuccess) { |
| 150 | + void executeCode(tab.id, (message) => { |
| 151 | + if (confirm(message)) { |
| 152 | + location.reload(); |
| 153 | + } |
| 154 | + }, globalOptions.reloadOnSuccess); |
| 155 | + } |
| 156 | + } |
| 157 | + async function handleTabActivated({ tabId }) { |
| 158 | + void updateItem(await getTabUrl(tabId).catch(() => '')); |
| 159 | + } |
| 160 | + async function handleClick({ checked, menuItemId }, tab) { |
| 161 | + if (menuItemId !== contextMenuId) { |
| 162 | + return; |
| 163 | + } |
| 164 | + try { |
| 165 | + await togglePermission(tab, checked); |
| 166 | + } |
| 167 | + catch (error) { |
| 168 | + if (tab === null || tab === void 0 ? void 0 : tab.id) { |
| 169 | + try { |
| 170 | + await executeCode(tab.id, 'alert' , |
| 171 | + String(error instanceof Error ? error : new Error(error.message))); |
| 172 | + } |
| 173 | + catch (_a) { |
| 174 | + alert(error); |
| 175 | + } |
| 176 | + void updateItem(); |
| 177 | + } |
| 178 | + throw error; |
| 179 | + } |
| 180 | + } |
| 181 | + function addDomainPermissionToggle(options) { |
| 182 | + if (!isBackgroundPage()) { |
| 183 | + throw new Error('webext-domain-permission-toggle can only be called from a background page'); |
| 184 | + } |
| 185 | + if (globalOptions) { |
| 186 | + throw new Error('webext-domain-permission-toggle can only be initialized once'); |
| 187 | + } |
| 188 | + const { name, optional_permissions } = chrome.runtime.getManifest(); |
| 189 | + globalOptions = { |
| 190 | + title: `Enable ${name} on this domain`, |
| 191 | + reloadOnSuccess: `Do you want to reload this page to apply ${name}?`, |
| 192 | + ...options |
| 193 | + }; |
| 194 | + if (!chrome.contextMenus) { |
| 195 | + throw new Error('webext-domain-permission-toggle requires the `contextMenu` permission'); |
| 196 | + } |
| 197 | + const optionalHosts = optional_permissions === null || optional_permissions === void 0 ? void 0 : optional_permissions.filter(permission => /<all_urls>|\*/.test(permission)); |
| 198 | + if (!optionalHosts || optionalHosts.length === 0) { |
| 199 | + throw new TypeError('webext-domain-permission-toggle some wildcard hosts to be specified in `optional_permissions`'); |
| 200 | + } |
| 201 | + chrome.contextMenus.remove(contextMenuId, () => chrome.runtime.lastError); |
| 202 | + chrome.contextMenus.create({ |
| 203 | + id: contextMenuId, |
| 204 | + type: 'checkbox', |
| 205 | + checked: false, |
| 206 | + title: globalOptions.title, |
| 207 | + contexts: ['page_action', 'browser_action'], |
| 208 | + documentUrlPatterns: optionalHosts |
| 209 | + }); |
| 210 | + chrome.contextMenus.onClicked.addListener(handleClick); |
| 211 | + chrome.tabs.onActivated.addListener(handleTabActivated); |
| 212 | + chrome.tabs.onUpdated.addListener(async (tabId, { status }, { url, active }) => { |
| 213 | + if (active && status === 'complete') { |
| 214 | + void updateItem(url !== null && url !== void 0 ? url : await getTabUrl(tabId).catch(() => '')); |
| 215 | + } |
| 216 | + }); |
| 217 | + } |
| 218 | + |
| 219 | + return addDomainPermissionToggle; |
132 | 220 |
|
133 | 221 | }());
|
0 commit comments