| import {grantedOptPermissions, isPermissionsObjectEmpty, missingPermissions} from './optionsPermissions.js'; |
| import {optionsPrototype} from './optionsPrototype'; |
| import specialOptions from './specialOptions.json5'; |
| |
| export {optionsPrototype, specialOptions}; |
| |
| // Adds missing options with their default value. If |dryRun| is set to false, |
| // they are also saved to the sync storage area. |
| export function cleanUpOptions(options, dryRun = false) { |
| console.debug('[cleanUpOptions] Previous options', JSON.stringify(options)); |
| |
| if (typeof options !== 'object' || options === null) options = {}; |
| |
| var ok = true; |
| for (const [opt, optMeta] of Object.entries(optionsPrototype)) { |
| if (!(opt in options) && |
| optMeta['killSwitchType'] !== 'internalKillSwitch') { |
| ok = false; |
| options[opt] = optMeta['defaultValue']; |
| } |
| } |
| |
| console.debug('[cleanUpOptions] New options', JSON.stringify(options)); |
| |
| if (!ok && !dryRun) { |
| chrome.storage.sync.set(options); |
| } |
| |
| return options; |
| } |
| |
| // This piece of code is used as part of the getOptions computation, and so |
| // isn't that useful. It's exported since we sometimes need to finish the |
| // computation in a service worker, where we have access to the |
| // chrome.permissions API. |
| // |
| // It accepts as an argument an object |items| with the same structure of the |
| // items saved in the sync storage area, and an array |permissionChecksFeatures| |
| // of features |
| export function disableItemsWithMissingPermissions( |
| items, permissionChecksFeatures) { |
| return grantedOptPermissions().then(grantedPerms => { |
| let permissionChecksPromises = []; |
| for (const f of permissionChecksFeatures) |
| permissionChecksPromises.push(missingPermissions(f, grantedPerms)); |
| |
| Promise.all(permissionChecksPromises).then(missingPerms => { |
| for (let i = 0; i < permissionChecksFeatures.length; i++) |
| if (!isPermissionsObjectEmpty(missingPerms[i])) |
| items[permissionChecksFeatures[i]] = false; |
| |
| return items; |
| }); |
| }); |
| } |
| |
| // Returns a promise which returns the values of options |options| which are |
| // stored in the sync storage area. |
| // |
| // |requireOptionalPermissions| will determine whether to check if the required |
| // optional permissions have been granted or not to the options which have such |
| // requirements. If it is true, features with missing permissions will have |
| // their value set to false. |
| // |
| // When a kill switch is active, affected options always have their value set to |
| // false. |
| |
| // #!if !production |
| let timerId = 0; |
| let randomId = btoa(Math.random().toString()).substr(10, 5); |
| // #!endif |
| export function getOptions(options, requireOptionalPermissions = true) { |
| // #!if !production |
| const timeLabel = 'getOptions--' + randomId + '-' + (timerId++); |
| const startMark = `mark_start_get_options_${timeLabel}`; |
| const grantedPermissionsCheckMark = |
| `mark_get_options_check_granted_permissions_${timeLabel}`; |
| const endMark = `mark_end_get_options_${timeLabel}`; |
| const measureName = `measure_get_options_${timeLabel}`; |
| window.performance.mark(startMark, {detail: {options}}); |
| // #!endif |
| // Once we only target MV3, this can be greatly simplified. |
| return new Promise((resolve, reject) => { |
| if (typeof options === 'string') |
| options = [options, '_forceDisabledFeatures']; |
| else if (Array.isArray(options)) |
| options = [...options, '_forceDisabledFeatures']; |
| else if (options !== null) |
| return reject(new Error( |
| 'Unexpected |options| parameter of type ' + (typeof options) + |
| ' (expected: string, array, or null).')); |
| |
| chrome.storage.sync.get(options, items => { |
| if (chrome.runtime.lastError) |
| return reject(chrome.runtime.lastError); |
| |
| // Handle applicable kill switches which force disable features |
| if (items?._forceDisabledFeatures) { |
| for (let feature of items._forceDisabledFeatures) { |
| items[feature] = false; |
| } |
| |
| delete items._forceDisabledFeatures; |
| } |
| |
| if (!requireOptionalPermissions) return resolve(items); |
| |
| // Check whether some options have missing permissions which would |
| // force disable these features |
| let permissionChecksFeatures = []; |
| for (const [key, value] of Object.entries(items)) |
| if ((key in optionsPrototype) && value && |
| optionsPrototype[key].requiredOptPermissions?.length) |
| permissionChecksFeatures.push(key); |
| |
| if (permissionChecksFeatures.length == 0) return resolve(items); |
| |
| // If we don't have access to the chrome.permissions API (content |
| // scripts don't have access to it[1]), do the final piece of |
| // computation in the service worker/background script. |
| // [1]: |
| // https://developer.chrome.com/docs/extensions/mv3/content_scripts/ |
| |
| // #!if !production |
| window.performance.mark( |
| grantedPermissionsCheckMark, {detail: {options}}); |
| // #!endif |
| if (!chrome.permissions) { |
| return chrome.runtime.sendMessage( |
| { |
| message: 'runDisableItemsWithMissingPermissions', |
| options: { |
| items, |
| permissionChecksFeatures, |
| }, |
| }, |
| response => { |
| if (response === undefined) |
| return reject(new Error( |
| 'An error ocurred while communicating with the service worker: ' + |
| chrome.runtime.lastError.message)); |
| |
| if (response.status == 'rejected') |
| return reject(response.error); |
| if (response.status == 'resolved') |
| return resolve(response.items); |
| return reject(new Error( |
| 'An unknown response was recieved from service worker.')); |
| }); |
| } |
| |
| disableItemsWithMissingPermissions(items, permissionChecksFeatures) |
| .then(finalItems => resolve(finalItems)) |
| .catch(err => reject(err)); |
| }); |
| }) |
| // #!if !production |
| .then(items => { |
| window.performance.mark(endMark, {detail: {options}}); |
| window.performance.measure(measureName, { |
| detail: {options}, |
| start: startMark, |
| end: endMark, |
| }); |
| return items; |
| }) |
| .catch(err => { |
| window.performance.mark(endMark, {detail: {options}}); |
| window.performance.measure(measureName, { |
| detail: {options}, |
| start: startMark, |
| end: endMark, |
| }); |
| throw err; |
| }) |
| // #!endif |
| ; |
| } |
| |
| // Returns a promise which returns whether the |option| option/feature is |
| // currently enabled. If the feature requires optional permissions to work, |
| // |requireOptionalPermissions| will determine whether to check if the required |
| // optional permissions have been granted or not. |
| export function isOptionEnabled(option, requireOptionalPermissions = true) { |
| return getOptions(option, requireOptionalPermissions).then(options => { |
| return options?.[option] === true; |
| }); |
| } |
| |
| export function getForceDisabledFeatures() { |
| return new Promise((res, rej) => { |
| chrome.storage.sync.get('_forceDisabledFeatures', items => { |
| if (chrome.runtime.lastError) return rej(chrome.runtime.lastError); |
| res(items?.['_forceDisabledFeatures'] ?? []); |
| }); |
| }); |
| } |