From 6eb1246508620e188adca1fa224551f4a5135e40 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 16 Jan 2021 10:35:56 -0500 Subject: [PATCH] Add `userSettings` entry to managed storage The managed `userSettings` entry is an array of entries, where each entry is a name/value pair encoded into an array of strings. The first item in the entry array is the name of a setting, and the second item is the stringified value for the setting. This is a more convenient way for administrators to set specific user settings. The settings set through `userSettings` policy will always be set at uBO launch time. --- platform/chromium/managed_storage.json | 9 ++++ src/js/background.js | 47 +++++++++-------- src/js/messaging.js | 8 +-- src/js/start.js | 31 +++++------ src/js/storage.js | 71 +++++++++++++++++++------- src/js/utils.js | 43 ++++++++++++++++ 6 files changed, 149 insertions(+), 60 deletions(-) diff --git a/platform/chromium/managed_storage.json b/platform/chromium/managed_storage.json index 7a334e241..25614dd05 100644 --- a/platform/chromium/managed_storage.json +++ b/platform/chromium/managed_storage.json @@ -16,6 +16,15 @@ "items": { "type": "string" } } }, + "userSettings": { + "title": "A list of [name,value] pairs to populate user settings", + "type": "array", + "items": { + "title": "A [name,value] pair", + "type": "array", + "items": { "type": "string" } + } + }, "disableDashboard": { "title": "Set to true to prevent access to configuration options", "type": "boolean" diff --git a/src/js/background.js b/src/js/background.js index 735e8af1e..d2e25b3a1 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -83,29 +83,32 @@ const µBlock = (( ) => { // jshint ignore:line userResourcesLocation: 'unset', }; + const userSettingsDefault = { + advancedUserEnabled: false, + alwaysDetachLogger: true, + autoUpdate: true, + cloudStorageEnabled: false, + collapseBlocked: true, + colorBlindFriendly: false, + contextMenuEnabled: true, + dynamicFilteringEnabled: false, + externalLists: [], + firewallPaneMinimized: true, + hyperlinkAuditingDisabled: true, + ignoreGenericCosmeticFilters: vAPI.webextFlavor.soup.has('mobile'), + largeMediaSize: 50, + parseAllABPHideFilters: true, + popupPanelSections: 0b111, + prefetchingDisabled: true, + requestLogMaxEntries: 1000, + showIconBadge: true, + tooltipsDisabled: false, + webrtcIPAddressHidden: false, + }; + return { - userSettings: { - advancedUserEnabled: false, - alwaysDetachLogger: true, - autoUpdate: true, - cloudStorageEnabled: false, - collapseBlocked: true, - colorBlindFriendly: false, - contextMenuEnabled: true, - dynamicFilteringEnabled: false, - externalLists: [], - firewallPaneMinimized: true, - hyperlinkAuditingDisabled: true, - ignoreGenericCosmeticFilters: vAPI.webextFlavor.soup.has('mobile'), - largeMediaSize: 50, - parseAllABPHideFilters: true, - popupPanelSections: 0b111, - prefetchingDisabled: true, - requestLogMaxEntries: 1000, - showIconBadge: true, - tooltipsDisabled: false, - webrtcIPAddressHidden: false, - }, + userSettingsDefault: userSettingsDefault, + userSettings: Object.assign({}, userSettingsDefault), hiddenSettingsDefault: hiddenSettingsDefault, hiddenSettingsAdmin: {}, diff --git a/src/js/messaging.js b/src/js/messaging.js index a8cb08450..a733f2522 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -912,12 +912,12 @@ const backupUserData = async function() { const userData = { timeStamp: Date.now(), version: vAPI.app.version, - userSettings: µb.userSettings, + userSettings: + µb.getModifiedSettings(µb.userSettings, µb.userSettingsDefault), selectedFilterLists: µb.selectedFilterLists, - hiddenSettings: µb.getModifiedHiddenSettings(), + hiddenSettings: + µb.getModifiedSettings(µb.hiddenSettings, µb.hiddenSettingsDefault), whitelist: µb.arrayFromWhitelist(µb.netWhitelist), - // String representation eventually to be deprecated - netWhitelist: µb.stringFromWhitelist(µb.netWhitelist), dynamicFilteringString: µb.permanentFirewall.toString(), urlFilteringString: µb.permanentURLFiltering.toString(), hostnameSwitchesString: µb.permanentSwitches.toString(), diff --git a/src/js/start.js b/src/js/start.js index 3ab9ca221..c1e41f9c8 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -160,30 +160,21 @@ const onNetWhitelistReady = function(netWhitelistRaw, adminExtra) { // User settings are in memory const onUserSettingsReady = function(fetched) { - const userSettings = µb.userSettings; - // List of external lists is meant to be an array if ( typeof fetched.externalLists === 'string' ) { fetched.externalLists = fetched.externalLists.trim().split(/[\n\r]+/); } - fromFetch(userSettings, fetched); + fromFetch(µb.userSettings, fetched); if ( µb.privacySettingsSupported ) { vAPI.browserSettings.set({ - 'hyperlinkAuditing': !userSettings.hyperlinkAuditingDisabled, - 'prefetching': !userSettings.prefetchingDisabled, - 'webrtcIPAddress': !userSettings.webrtcIPAddressHidden + 'hyperlinkAuditing': !µb.userSettings.hyperlinkAuditingDisabled, + 'prefetching': !µb.userSettings.prefetchingDisabled, + 'webrtcIPAddress': !µb.userSettings.webrtcIPAddressHidden }); } - - µb.permanentFirewall.fromString(fetched.dynamicFilteringString); - µb.sessionFirewall.assign(µb.permanentFirewall); - µb.permanentURLFiltering.fromString(fetched.urlFilteringString); - µb.sessionURLFiltering.assign(µb.permanentURLFiltering); - µb.permanentSwitches.fromString(fetched.hostnameSwitchesString); - µb.sessionSwitches.assign(µb.permanentSwitches); }; /******************************************************************************/ @@ -219,8 +210,15 @@ const onFirstFetchReady = function(fetched, adminExtra) { // Order is important -- do not change: fromFetch(µb.localSettings, fetched); - onUserSettingsReady(fetched); fromFetch(µb.restoreBackupSettings, fetched); + + µb.permanentFirewall.fromString(fetched.dynamicFilteringString); + µb.sessionFirewall.assign(µb.permanentFirewall); + µb.permanentURLFiltering.fromString(fetched.urlFilteringString); + µb.sessionURLFiltering.assign(µb.permanentURLFiltering); + µb.permanentSwitches.fromString(fetched.hostnameSwitchesString); + µb.sessionSwitches.assign(µb.permanentSwitches); + onNetWhitelistReady(fetched.netWhitelist, adminExtra); onVersionReady(fetched.version); }; @@ -269,7 +267,6 @@ const createDefaultProps = function() { fetchableProps.hostnameSwitchesString += '\nno-csp-reports: * true'; } toFetch(µb.localSettings, fetchableProps); - toFetch(µb.userSettings, fetchableProps); toFetch(µb.restoreBackupSettings, fetchableProps); return fetchableProps; }; @@ -321,6 +318,10 @@ try { log.info(`First fetch ready ${Date.now()-vAPI.T0} ms after launch`); onFirstFetchReady(fetched, adminExtra); }), + µb.loadUserSettings().then(fetched => { + log.info(`User settings ready ${Date.now()-vAPI.T0} ms after launch`); + onUserSettingsReady(fetched); + }), µb.loadPublicSuffixList().then(( ) => { log.info(`PSL ready ${Date.now()-vAPI.T0} ms after launch`); }), diff --git a/src/js/storage.js b/src/js/storage.js index 59f3df9e2..c48099fe9 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -83,8 +83,49 @@ /******************************************************************************/ +µBlock.loadUserSettings = async function() { + const usDefault = this.userSettingsDefault; + + const results = await Promise.all([ + vAPI.storage.get(Object.assign(usDefault)), + vAPI.adminStorage.get('userSettings'), + ]); + + const usUser = results[0] instanceof Object && results[0] || + Object.assign(usDefault); + + if ( Array.isArray(results[1]) ) { + const adminSettings = results[1]; + for ( const entry of adminSettings ) { + if ( entry.length < 1 ) { continue; } + const name = entry[0]; + if ( usDefault.hasOwnProperty(name) === false ) { continue; } + const value = entry.length < 2 + ? usDefault[name] + : this.settingValueFromString(usDefault, name, entry[1]); + if ( value === undefined ) { continue; } + usUser[name] = usDefault[name] = value; + } + } + + return usUser; +}; + µBlock.saveUserSettings = function() { - vAPI.storage.set(this.userSettings); + const toSave = this.getModifiedSettings( + this.userSettings, + this.userSettingsDefault + ); + const toRemove = []; + for ( const key in this.userSettings ) { + if ( this.userSettings.hasOwnProperty(key) === false ) { continue; } + if ( toSave.hasOwnProperty(key) === false ) { continue; } + toRemove.push(key); + } + if ( toRemove.length !== 0 ) { + vAPI.storage.remove(toRemove); + } + vAPI.storage.set(toSave); }; /******************************************************************************/ @@ -164,24 +205,16 @@ }; // Note: Save only the settings which values differ from the default ones. -// This way the new default values in the future will properly apply for those -// which were not modified by the user. - -µBlock.getModifiedHiddenSettings = function() { - const out = {}; - for ( const prop in this.hiddenSettings ) { - if ( - this.hiddenSettings.hasOwnProperty(prop) && - this.hiddenSettings[prop] !== this.hiddenSettingsDefault[prop] - ) { - out[prop] = this.hiddenSettings[prop]; - } - } - return out; -}; +// This way the new default values in the future will properly apply for +// those which were not modified by the user. µBlock.saveHiddenSettings = function() { - vAPI.storage.set({ hiddenSettings: this.getModifiedHiddenSettings() }); + vAPI.storage.set({ + hiddenSettings: this.getModifiedSettings( + this.hiddenSettings, + this.hiddenSettingsDefault + ) + }); }; self.addEventListener('hiddenSettingsChanged', ( ) => { @@ -404,7 +437,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { const result = Array.from(selectedListKeySet); if ( externalLists.join() !== this.userSettings.externalLists.join() ) { this.userSettings.externalLists = externalLists; - vAPI.storage.set({ externalLists }); + this.saveUserSettings(); } this.saveSelectedFilterLists(result); }; @@ -599,7 +632,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { this.assets.registerAssetSource(listURL, newEntry); importedListKeys.push(listURL); this.userSettings.externalLists.push(listURL.trim()); - vAPI.storage.set({ externalLists: this.userSettings.externalLists }); + this.saveUserSettings(); this.saveSelectedFilterLists([ listURL ], true); }; diff --git a/src/js/utils.js b/src/js/utils.js index 57f7370a1..b582a9359 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -684,3 +684,46 @@ window.dispatchEvent(new CustomEvent(name)); } }; + +/******************************************************************************/ + +µBlock.getModifiedSettings = function(edit, orig = {}) { + const out = {}; + for ( const prop in edit ) { + if ( orig.hasOwnProperty(prop) && edit[prop] !== orig[prop] ) { + out[prop] = edit[prop]; + } + } + return out; +}; + +µBlock.settingValueFromString = function(orig, name, s) { + if ( typeof name !== 'string' || typeof s !== 'string' ) { return; } + if ( orig.hasOwnProperty(name) === false ) { return; } + let r; + switch ( typeof orig[name] ) { + case 'boolean': + if ( s === 'true' ) { + r = true; + } else if ( s === 'false' ) { + r = false; + } + break; + case 'string': + r = s.trim(); + break; + case 'number': + if ( s.startsWith('0b') ) { + r = parseInt(s.slice(2), 2); + } else if ( s.startsWith('0x') ) { + r = parseInt(s.slice(2), 16); + } else { + r = parseInt(s, 10); + } + if ( isNaN(r) ) { r = undefined; } + break; + default: + break; + } + return r; +};