1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-10-06 01:27:12 +02:00

Properly handle default list status changes in assets.json

This commit fix properly handling toggling off the default
status of a list such that the list will be automatically
turned off when its status change from default to non-default.

Additionally, imported lists which become stock lists will
be properly migrated from imported lists section.
This commit is contained in:
Raymond Hill 2023-03-23 13:40:51 -04:00
parent 6d989744bb
commit 2cd062898c
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
4 changed files with 162 additions and 103 deletions

View File

@ -4,7 +4,7 @@ run_options := $(filter-out $@,$(MAKECMDGOALS))
.PHONY: all clean test lint chromium opera firefox npm dig mv3 mv3-quick \ .PHONY: all clean test lint chromium opera firefox npm dig mv3 mv3-quick \
compare maxcost medcost mincost modifiers record wasm compare maxcost medcost mincost modifiers record wasm
sources := $(wildcard assets/resources/* dist/version src/* src/*/* src/*/*/* src/*/*/*/*) sources := $(wildcard assets/* assets/*/* dist/version src/* src/*/* src/*/*/* src/*/*/*/*)
platform := $(wildcard platform/* platform/*/* platform/*/*/* platform/*/*/*/*) platform := $(wildcard platform/* platform/*/* platform/*/*/* platform/*/*/*/*)
assets := dist/build/uAssets assets := dist/build/uAssets

View File

@ -379,22 +379,21 @@ const getAssetSourceRegistry = function() {
return assetSourceRegistryPromise; return assetSourceRegistryPromise;
}; };
const registerAssetSource = function(assetKey, dict) { const registerAssetSource = function(assetKey, newDict) {
const entry = assetSourceRegistry[assetKey] || {}; const currentDict = assetSourceRegistry[assetKey] || {};
for ( const prop in dict ) { for ( const [ k, v ] of Object.entries(newDict) ) {
if ( dict.hasOwnProperty(prop) === false ) { continue; } if ( v === undefined || v === null ) {
if ( dict[prop] === undefined ) { delete currentDict[k];
delete entry[prop];
} else { } else {
entry[prop] = dict[prop]; currentDict[k] = newDict[k];
} }
} }
let contentURL = dict.contentURL; let contentURL = newDict.contentURL;
if ( contentURL !== undefined ) { if ( contentURL !== undefined ) {
if ( typeof contentURL === 'string' ) { if ( typeof contentURL === 'string' ) {
contentURL = entry.contentURL = [ contentURL ]; contentURL = currentDict.contentURL = [ contentURL ];
} else if ( Array.isArray(contentURL) === false ) { } else if ( Array.isArray(contentURL) === false ) {
contentURL = entry.contentURL = []; contentURL = currentDict.contentURL = [];
} }
let remoteURLCount = 0; let remoteURLCount = 0;
for ( let i = 0; i < contentURL.length; i++ ) { for ( let i = 0; i < contentURL.length; i++ ) {
@ -402,18 +401,18 @@ const registerAssetSource = function(assetKey, dict) {
remoteURLCount += 1; remoteURLCount += 1;
} }
} }
entry.hasLocalURL = remoteURLCount !== contentURL.length; currentDict.hasLocalURL = remoteURLCount !== contentURL.length;
entry.hasRemoteURL = remoteURLCount !== 0; currentDict.hasRemoteURL = remoteURLCount !== 0;
} else if ( entry.contentURL === undefined ) { } else if ( currentDict.contentURL === undefined ) {
entry.contentURL = []; currentDict.contentURL = [];
} }
if ( typeof entry.updateAfter !== 'number' ) { if ( typeof currentDict.updateAfter !== 'number' ) {
entry.updateAfter = 5; currentDict.updateAfter = 5;
} }
if ( entry.submitter ) { if ( currentDict.submitter ) {
entry.submitTime = Date.now(); // To detect stale entries currentDict.submitTime = Date.now(); // To detect stale entries
} }
assetSourceRegistry[assetKey] = entry; assetSourceRegistry[assetKey] = currentDict;
}; };
const unregisterAssetSource = function(assetKey) { const unregisterAssetSource = function(assetKey) {
@ -443,12 +442,18 @@ const updateAssetSourceRegistry = function(json, silent = false) {
let newDict; let newDict;
try { try {
newDict = JSON.parse(json); newDict = JSON.parse(json);
newDict['assets.json'].defaultListset =
Array.from(Object.entries(newDict))
.filter(a => a[1].content === 'filters' && a[1].off === undefined)
.map(a => a[0]);
} catch (ex) { } catch (ex) {
} }
if ( newDict instanceof Object === false ) { return; } if ( newDict instanceof Object === false ) { return; }
const oldDict = assetSourceRegistry; const oldDict = assetSourceRegistry;
fireNotification('assets.json-updated', { newDict, oldDict });
// Remove obsolete entries (only those which were built-in). // Remove obsolete entries (only those which were built-in).
for ( const assetKey in oldDict ) { for ( const assetKey in oldDict ) {
if ( if (
@ -1005,7 +1010,16 @@ const updateNext = async function() {
// In auto-update context, be gentle on remote servers. // In auto-update context, be gentle on remote servers.
remoteServerFriendly = updaterAuto; remoteServerFriendly = updaterAuto;
const result = await getRemote(toUpdate[0]); let result;
if (
toUpdate[0] !== 'assets.json' ||
µb.hiddenSettings.debugAssetsJson !== true
) {
result = await getRemote(toUpdate[0]);
} else {
result = await assets.fetchText('/assets/assets.json');
result.assetKey = 'assets.json';
}
remoteServerFriendly = false; remoteServerFriendly = false;

View File

@ -65,6 +65,7 @@ const hiddenSettingsDefault = {
cnameReplayFullURL: false, cnameReplayFullURL: false,
cnameUncloakProxied: false, cnameUncloakProxied: false,
consoleLogLevel: 'unset', consoleLogLevel: 'unset',
debugAssetsJson: false,
debugScriptlets: false, debugScriptlets: false,
debugScriptletInjector: false, debugScriptletInjector: false,
disableWebAssembly: false, disableWebAssembly: false,

View File

@ -649,22 +649,21 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
/******************************************************************************/ /******************************************************************************/
µb.getAvailableLists = async function() { µb.getAvailableLists = async function() {
let oldAvailableLists = {}, const newAvailableLists = {};
newAvailableLists = {};
// User filter list. // User filter list
newAvailableLists[this.userFiltersPath] = { newAvailableLists[this.userFiltersPath] = {
content: 'filters', content: 'filters',
group: 'user', group: 'user',
title: i18n$('1pPageName'), title: i18n$('1pPageName'),
}; };
// Custom filter lists. // Custom filter lists
const importedListKeys = this.listKeysFromCustomFilterLists( const importedListKeys = new Set(
this.userSettings.importedLists this.listKeysFromCustomFilterLists(this.userSettings.importedLists)
); );
for ( const listKey of importedListKeys ) { for ( const listKey of importedListKeys ) {
const entry = { const asset = {
content: 'filters', content: 'filters',
contentURL: listKey, contentURL: listKey,
external: true, external: true,
@ -672,45 +671,17 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
submitter: 'user', submitter: 'user',
title: '', title: '',
}; };
newAvailableLists[listKey] = entry; newAvailableLists[listKey] = asset;
io.registerAssetSource(listKey, entry); io.registerAssetSource(listKey, asset);
} }
// Convert a no longer existing stock list into an imported list, except // Load previously saved available lists -- these contains data
// when the removed stock list is deemed a "bad list". // computed at run-time, we will reuse this data if possible
const customListFromStockList = assetKey => { const [ bin, registeredAssets, badlists ] = await Promise.all([
const oldEntry = oldAvailableLists[assetKey];
if ( oldEntry === undefined || oldEntry.off === true ) { return; }
let listURL = oldEntry.contentURL;
if ( Array.isArray(listURL) ) {
listURL = listURL[0];
}
if ( this.badLists.has(listURL) ) { return; }
const newEntry = {
content: 'filters',
contentURL: listURL,
external: true,
group: 'custom',
submitter: 'user',
title: oldEntry.title || ''
};
newAvailableLists[listURL] = newEntry;
io.registerAssetSource(listURL, newEntry);
importedListKeys.push(listURL);
this.userSettings.importedLists.push(listURL.trim());
this.saveUserSettings();
this.saveSelectedFilterLists([ listURL ], true);
};
const promises = [
vAPI.storage.get('availableFilterLists'), vAPI.storage.get('availableFilterLists'),
io.metadata(), io.metadata(),
this.badLists.size === 0 ? io.get('ublock-badlists') : false, this.badLists.size === 0 ? io.get('ublock-badlists') : false,
]; ]);
// Load previously saved available lists -- these contains data
// computed at run-time, we will reuse this data if possible.
const [ bin, entries, badlists ] = await Promise.all(promises);
if ( badlists instanceof Object ) { if ( badlists instanceof Object ) {
for ( const line of badlists.content.split(/\s*[\n\r]+\s*/) ) { for ( const line of badlists.content.split(/\s*[\n\r]+\s*/) ) {
@ -721,51 +692,97 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
} }
} }
oldAvailableLists = bin && bin.availableFilterLists || {}; const oldAvailableLists = bin && bin.availableFilterLists || {};
for ( const assetKey in entries ) { for ( const [ assetKey, asset ] of Object.entries(registeredAssets) ) {
if ( entries.hasOwnProperty(assetKey) === false ) { continue; } if ( asset.content !== 'filters' ) { continue; }
const entry = entries[assetKey]; newAvailableLists[assetKey] = Object.assign({}, asset);
if ( entry.content !== 'filters' ) { continue; }
newAvailableLists[assetKey] = Object.assign({}, entry);
} }
// Load set of currently selected filter lists. // Load set of currently selected filter lists
const listKeySet = new Set(this.selectedFilterLists); const selectedListset = new Set(this.selectedFilterLists);
for ( const listKey in newAvailableLists ) {
if ( newAvailableLists.hasOwnProperty(listKey) ) {
newAvailableLists[listKey].off = !listKeySet.has(listKey);
}
}
//finalize(); // Remove imported filter lists which are already present in stock lists
// Final steps: for ( const [ stockAssetKey, stockEntry ] of Object.entries(newAvailableLists) ) {
// - reuse existing list metadata if any; if ( stockEntry.content !== 'filters' ) { continue; }
// - unregister unreferenced imported filter lists if any. if ( stockEntry.group === 'user' ) { continue; }
// Reuse existing metadata. if ( stockEntry.submitter === 'user' ) { continue; }
for ( const assetKey in oldAvailableLists ) { if ( stockAssetKey.includes('://') ) { continue; }
const oldEntry = oldAvailableLists[assetKey]; const contentURLs = Array.isArray(stockEntry.contentURL)
const newEntry = newAvailableLists[assetKey]; ? stockEntry.contentURL
// List no longer exists. If a stock list, try to convert to : [ stockEntry.contentURL ];
// imported list if it was selected. for ( const importedAssetKey of contentURLs ) {
if ( newEntry === undefined ) { const importedEntry = newAvailableLists[importedAssetKey];
this.removeFilterList(assetKey); if ( importedEntry === undefined ) { continue; }
if ( assetKey.indexOf('://') === -1 ) { delete newAvailableLists[importedAssetKey];
customListFromStockList(assetKey); io.unregisterAssetSource(importedAssetKey);
this.removeFilterList(importedAssetKey);
if ( selectedListset.has(importedAssetKey) ) {
selectedListset.add(stockAssetKey);
selectedListset.delete(importedAssetKey);
} }
continue; importedListKeys.delete(importedAssetKey);
break;
} }
}
// Unregister lists in old listset not present in new listset.
// Convert a no longer existing stock list into an imported list, except
// when the removed stock list is deemed a "bad list".
for ( const [ assetKey, oldEntry ] of Object.entries(oldAvailableLists) ) {
if ( newAvailableLists[assetKey] !== undefined ) { continue; }
const on = selectedListset.delete(assetKey);
this.removeFilterList(assetKey);
io.unregisterAssetSource(assetKey);
if ( assetKey.includes('://') ) { continue; }
if ( on === false ) { continue; }
const listURL = Array.isArray(oldEntry.contentURL)
? oldEntry.contentURL[0]
: oldEntry.contentURL;
if ( this.badLists.has(listURL) ) { continue; }
const newEntry = {
content: 'filters',
contentURL: listURL,
external: true,
group: 'custom',
submitter: 'user',
title: oldEntry.title || ''
};
newAvailableLists[listURL] = newEntry;
io.registerAssetSource(listURL, newEntry);
importedListKeys.add(listURL);
selectedListset.add(listURL);
}
// Remove unreferenced imported filter lists
for ( const [ assetKey, asset ] of Object.entries(newAvailableLists) ) {
if ( asset.submitter !== 'user' ) { continue; }
if ( importedListKeys.has(assetKey) ) { continue; }
selectedListset.delete(assetKey);
delete newAvailableLists[assetKey];
this.removeFilterList(assetKey);
io.unregisterAssetSource(assetKey);
}
// Mark lists as disabled/enabled according to selected listset
for ( const [ assetKey, asset ] of Object.entries(newAvailableLists) ) {
asset.off = selectedListset.has(assetKey) === false;
}
// Reuse existing metadata
for ( const [ assetKey, oldEntry ] of Object.entries(oldAvailableLists) ) {
const newEntry = newAvailableLists[assetKey];
if ( newEntry === undefined ) { continue; }
if ( oldEntry.entryCount !== undefined ) { if ( oldEntry.entryCount !== undefined ) {
newEntry.entryCount = oldEntry.entryCount; newEntry.entryCount = oldEntry.entryCount;
} }
if ( oldEntry.entryUsedCount !== undefined ) { if ( oldEntry.entryUsedCount !== undefined ) {
newEntry.entryUsedCount = oldEntry.entryUsedCount; newEntry.entryUsedCount = oldEntry.entryUsedCount;
} }
// This may happen if the list name was pulled from the list // This may happen if the list name was pulled from the list content
// content.
// https://github.com/chrisaljoudi/uBlock/issues/982 // https://github.com/chrisaljoudi/uBlock/issues/982
// There is no guarantee the title was successfully extracted from // There is no guarantee the title was successfully extracted from
// the list content. // the list content
if ( if (
newEntry.title === '' && newEntry.title === '' &&
typeof oldEntry.title === 'string' && typeof oldEntry.title === 'string' &&
@ -775,14 +792,13 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
} }
} }
// Remove unreferenced imported filter lists. if ( Array.from(importedListKeys).join('\n') !== this.userSettings.importedLists.join('\n') ) {
for ( const assetKey in newAvailableLists ) { this.userSettings.importedLists = Array.from(importedListKeys);
const newEntry = newAvailableLists[assetKey]; this.saveUserSettings();
if ( newEntry.submitter !== 'user' ) { continue; } }
if ( importedListKeys.indexOf(assetKey) !== -1 ) { continue; }
delete newAvailableLists[assetKey]; if ( Array.from(selectedListset).join() !== this.selectedFilterLists.join() ) {
io.unregisterAssetSource(assetKey); this.saveSelectedFilterLists(Array.from(selectedListset));
this.removeFilterList(assetKey);
} }
return newAvailableLists; return newAvailableLists;
@ -1580,7 +1596,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
if ( topic === 'builtin-asset-source-added' ) { if ( topic === 'builtin-asset-source-added' ) {
if ( details.entry.content === 'filters' ) { if ( details.entry.content === 'filters' ) {
if ( if (
details.entry.off !== true || details.entry.off === true &&
this.listMatchesEnvironment(details.entry) this.listMatchesEnvironment(details.entry)
) { ) {
this.saveSelectedFilterLists([ details.assetKey ], true); this.saveSelectedFilterLists([ details.assetKey ], true);
@ -1588,4 +1604,32 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
} }
return; return;
} }
if ( topic === 'assets.json-updated' ) {
const { newDict, oldDict } = details;
const newDefaultListset = new Set(newDict['assets.json'].defaultListset || []);
const oldDefaultListset = new Set(oldDict['assets.json'].defaultListset || []);
if ( oldDefaultListset.size === 0 ) {
Array.from(Object.entries(newDict))
.filter(a => a[1].content === 'filters' && a[1].off === undefined)
.map(a => a[0])
.forEach(a => oldDefaultListset.add(a));
}
const selectedListset = new Set(this.selectedFilterLists);
let selectedListModified = false;
for ( const assetKey of oldDefaultListset ) {
if ( newDefaultListset.has(assetKey) ) { continue; }
selectedListset.delete(assetKey);
selectedListModified = true;
}
for ( const assetKey of newDefaultListset ) {
if ( oldDefaultListset.has(assetKey) ) { continue; }
selectedListset.add(assetKey);
selectedListModified = true;
}
if ( selectedListModified ) {
this.saveSelectedFilterLists(Array.from(selectedListset));
}
return;
}
}; };