Add support for admin-managed hidden settings

Related discussion:
- https://github.com/uBlockOrigin/uBlock-issues/issues/1437#issuecomment-754127066
This commit is contained in:
Raymond Hill 2021-01-05 12:16:50 -05:00
parent d254c7c304
commit c1130ec843
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
9 changed files with 162 additions and 65 deletions

View File

@ -7,13 +7,37 @@
"description": "All entries present will overwrite local settings.",
"type": "string"
},
"extraTrustedSiteDirectives": {
"title": "A list of trusted-site directives",
"description": "Trusted-site directives to always add at launch time.",
"type": "array",
"items": {
"type": "string"
"toSet": {
"title": "Settings to overwrite at launch time",
"type": "object",
"properties": {
"hiddenSettings": {
"title": "A list of [name,value] pairs to populate hidden settings",
"type": "array",
"items": {
"title": "A [name,value] pair",
"type": "array",
"items": { "type": "string" }
}
},
"trustedSiteDirectives": {
"title": "A list of trusted-site directives",
"type": "array",
"items": { "type": "string" }
}
}
},
"toAdd": {
"title": "Settings to add at launch time",
"type": "object",
"properties": {
"trustedSiteDirectives": {
"title": "A list of trusted-site directives",
"description": "Trusted-site directives to always add at launch time.",
"type": "array",
"items": { "type": "string" }
}
},
}
}
}

View File

@ -1429,9 +1429,10 @@ vAPI.adminStorage = (( ) => {
bin = await webext.storage.managed.get(key);
} catch(ex) {
}
if ( bin instanceof Object ) {
if ( typeof key === 'string' && bin instanceof Object ) {
return bin[key];
}
return bin;
}
};
})();

View File

@ -91,6 +91,9 @@
text-decoration: underline var(--sf-warning-ink);
text-underline-position: under;
}
.cm-s-default .cm-readonly {
color: var(--sf-readonly-ink);
}
/* Rules */
.cm-s-default .cm-allowrule {

View File

@ -192,6 +192,7 @@
--sf-error-surface: #ff000016;
--sf-keyword-ink: var(--purple-60);
--sf-notice-ink: var(--light-gray-60);
--sf-readonly-ink: var(--light-gray-80);
--sf-tag-ink: #117700;
--sf-value-ink: var(--orange-80);
--sf-variable-ink: var(--default-ink);

View File

@ -31,6 +31,7 @@
/******************************************************************************/
let defaultSettings = new Map();
let adminSettings = new Map();
let beforeHash = '';
/******************************************************************************/
@ -45,18 +46,22 @@ CodeMirror.defineMode('raw-settings', function() {
const match = stream.match(/\S+/);
if ( match !== null && defaultSettings.has(match[0]) ) {
lastSetting = match[0];
return 'keyword';
return adminSettings.has(match[0])
? 'readonly keyword'
: 'keyword';
}
stream.skipToEnd();
return 'line-cm-error';
}
stream.eatSpace();
const match = stream.match(/.*$/);
if (
match !== null &&
match[0].trim() !== defaultSettings.get(lastSetting)
) {
return 'line-cm-strong';
if ( match !== null ) {
if ( match[0].trim() !== defaultSettings.get(lastSetting) ) {
return 'line-cm-strong';
}
if ( adminSettings.has(lastSetting) ) {
return 'readonly';
}
}
stream.skipToEnd();
return null;
@ -146,21 +151,34 @@ const renderAdvancedSettings = async function(first) {
what: 'readHiddenSettings',
});
defaultSettings = new Map(arrayFromObject(details.default));
adminSettings = new Map(arrayFromObject(details.admin));
beforeHash = hashFromAdvancedSettings(details.current);
const pretty = [];
const roLines = [];
const entries = arrayFromObject(details.current);
let max = 0;
for ( const [ k ] of entries ) {
if ( k.length > max ) { max = k.length; }
}
for ( const [ k, v ] of entries ) {
for ( let i = 0; i < entries.length; i++ ) {
const [ k, v ] = entries[i];
pretty.push(' '.repeat(max - k.length) + `${k} ${v}`);
if ( adminSettings.has(k) ) {
roLines.push(i);
}
}
pretty.push('');
cmEditor.setValue(pretty.join('\n'));
if ( first ) {
cmEditor.clearHistory();
}
for ( const line of roLines ) {
cmEditor.markText(
{ line, ch: 0 },
{ line: line + 1, ch: 0 },
{ readOnly: true }
);
}
advancedSettingsChanged();
cmEditor.focus();
};

View File

@ -108,6 +108,7 @@ const µBlock = (( ) => { // jshint ignore:line
},
hiddenSettingsDefault: hiddenSettingsDefault,
hiddenSettingsAdmin: {},
hiddenSettings: Object.assign({}, hiddenSettingsDefault),
// Features detection.

View File

@ -1258,8 +1258,9 @@ const onMessage = function(request, sender, callback) {
case 'readHiddenSettings':
response = {
current: µb.hiddenSettings,
default: µb.hiddenSettingsDefault,
'default': µb.hiddenSettingsDefault,
'admin': µb.hiddenSettingsAdmin,
'current': µb.hiddenSettings,
};
break;

View File

@ -143,10 +143,10 @@ const onNetWhitelistReady = function(netWhitelistRaw, adminExtra) {
}
// Append admin-controlled trusted-site directives
if (
Array.isArray(adminExtra.trustedSites) &&
adminExtra.trustedSites.length !== 0
adminExtra instanceof Object &&
Array.isArray(adminExtra.trustedSiteDirectives)
) {
for ( const directive of adminExtra.trustedSites ) {
for ( const directive of adminExtra.trustedSiteDirectives ) {
µb.netWhitelistDefault.push(directive);
netWhitelistRaw.push(directive);
}
@ -296,9 +296,7 @@ try {
);
log.info(`Backend storage for cache will be ${cacheBackend}`);
const adminExtra = {};
adminExtra.trustedSites =
await vAPI.adminStorage.get('extraTrustedSiteDirectives') || [];
const adminExtra = await vAPI.adminStorage.get('toAdd');
log.info(`Extra admin settings ready ${Date.now()-vAPI.T0} ms after launch`);
// https://github.com/uBlockOrigin/uBlock-issues/issues/1365

View File

@ -89,29 +89,51 @@
/******************************************************************************/
µBlock.loadHiddenSettings = async function() {
const bin = await vAPI.storage.get('hiddenSettings');
if ( bin instanceof Object === false ) { return; }
// Admin hidden settings have precedence over user hidden settings.
const hs = bin.hiddenSettings;
if ( hs instanceof Object ) {
const hsDefault = this.hiddenSettingsDefault;
for ( const key in hsDefault ) {
if (
hsDefault.hasOwnProperty(key) &&
hs.hasOwnProperty(key) &&
typeof hs[key] === typeof hsDefault[key]
) {
this.hiddenSettings[key] = hs[key];
}
}
if ( typeof this.hiddenSettings.suspendTabsUntilReady === 'boolean' ) {
this.hiddenSettings.suspendTabsUntilReady =
this.hiddenSettings.suspendTabsUntilReady
? 'yes'
: 'unset';
µBlock.loadHiddenSettings = async function() {
const hsDefault = this.hiddenSettingsDefault;
const hsAdmin = this.hiddenSettingsAdmin;
const hsUser = this.hiddenSettings;
const results = await Promise.all([
vAPI.adminStorage.get('toSet'),
vAPI.storage.get('hiddenSettings'),
]);
if (
results[0] instanceof Object &&
Array.isArray(results[0].hiddenSettings)
) {
for ( const entry of results[0].hiddenSettings ) {
if ( entry.length < 1 ) { continue; }
const name = entry[0];
if ( hsDefault.hasOwnProperty(name) === false ) { continue; }
const value = entry.length < 2
? hsDefault[name]
: this.hiddenSettingValueFromString(name, entry[1]);
if ( value === undefined ) { continue; }
hsDefault[name] = hsAdmin[name] = hsUser[name] = value;
}
}
const hs = results[1] instanceof Object && results[1].hiddenSettings || {};
if ( Object.keys(hsAdmin).length === 0 && Object.keys(hs).length === 0 ) {
return;
}
for ( const key in hsDefault ) {
if ( hsDefault.hasOwnProperty(key) === false ) { continue; }
if ( hsAdmin.hasOwnProperty(name) ) { continue; }
if ( typeof hs[key] !== typeof hsDefault[key] ) { continue; }
this.hiddenSettings[key] = hs[key];
}
if ( typeof this.hiddenSettings.suspendTabsUntilReady === 'boolean' ) {
this.hiddenSettings.suspendTabsUntilReady =
this.hiddenSettings.suspendTabsUntilReady
? 'yes'
: 'unset';
}
this.fireDOMEvent('hiddenSettingsChanged');
};
@ -162,31 +184,47 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
if ( matches === null || matches.length !== 3 ) { continue; }
const name = matches[1];
if ( out.hasOwnProperty(name) === false ) { continue; }
const value = matches[2];
switch ( typeof out[name] ) {
case 'boolean':
if ( value === 'true' ) {
out[name] = true;
} else if ( value === 'false' ) {
out[name] = false;
}
break;
case 'string':
out[name] = value.trim();
break;
case 'number':
out[name] = parseInt(value, 10);
if ( isNaN(out[name]) ) {
out[name] = this.hiddenSettingsDefault[name];
}
break;
default:
break;
if ( this.hiddenSettingsAdmin.hasOwnProperty(name) ) { continue; }
const value = this.hiddenSettingValueFromString(name, matches[2]);
if ( value !== undefined ) {
out[name] = value;
}
}
return out;
};
µBlock.hiddenSettingValueFromString = function(name, value) {
if ( typeof name !== 'string' || typeof value !== 'string' ) { return; }
const hsDefault = this.hiddenSettingsDefault;
if ( hsDefault.hasOwnProperty(name) === false ) { return; }
let r;
switch ( typeof hsDefault[name] ) {
case 'boolean':
if ( value === 'true' ) {
r = true;
} else if ( value === 'false' ) {
r = false;
}
break;
case 'string':
r = value.trim();
break;
case 'number':
if ( value.startsWith('0b') ) {
r = parseInt(value.slice(2), 2);
} else if ( value.startsWith('0x') ) {
r = parseInt(value.slice(2), 16);
} else {
r = parseInt(value, 10);
}
if ( isNaN(r) ) { r = undefined; }
break;
default:
break;
}
return r;
};
µBlock.stringFromHiddenSettings = function() {
const out = [];
for ( const key of Object.keys(this.hiddenSettings).sort() ) {
@ -1222,9 +1260,17 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
// values are left to the user's choice.
µBlock.restoreAdminSettings = async function() {
let toSet = {};
let data;
try {
const json = await vAPI.adminStorage.get('adminSettings');
const store = await vAPI.adminStorage.get([
'adminSettings',
'toSet',
]) || {};
if ( store.toSet instanceof Object ) {
toSet = store.toSet;
}
const json = store.adminSettings;
if ( typeof json === 'string' && json !== '' ) {
data = JSON.parse(json);
} else if ( json instanceof Object ) {
@ -1234,7 +1280,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
console.error(ex);
}
if ( data instanceof Object === false ) { return; }
if ( data instanceof Object === false ) { data = {}; }
const bin = {};
let binNotEmpty = false;
@ -1269,7 +1315,11 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
binNotEmpty = true;
}
if ( Array.isArray(data.whitelist) ) {
if ( Array.isArray(toSet.trustedSiteDirectives) ) {
µBlock.netWhitelistDefault = toSet.trustedSiteDirectives.slice();
bin.netWhitelist = toSet.trustedSiteDirectives.slice();
binNotEmpty = true;
} else if ( Array.isArray(data.whitelist) ) {
bin.netWhitelist = data.whitelist;
binNotEmpty = true;
} else if ( typeof data.netWhitelist === 'string' ) {