diff --git a/src/css/3p-filters.css b/src/css/3p-filters.css
index 8aa6760f9..b3687bae1 100644
--- a/src/css/3p-filters.css
+++ b/src/css/3p-filters.css
@@ -9,14 +9,24 @@ ul {
#options li {
margin-bottom: 0.5em;
}
+#listsOfBlockedHostsPrompt {
+ cursor: pointer;
+ }
+#listsOfBlockedHostsPrompt:before {
+ color: #aaa;
+ content: '\2212 ';
+ }
+body.hideUnused #listsOfBlockedHostsPrompt:before {
+ content: '+ ';
+ }
#lists {
margin: 0.5em 0 0 0;
- padding-left: 1em;
+ padding-left: 0.5em;
padding-right: 0em;
}
body[dir=rtl] #lists {
padding-left: 0em;
- padding-right: 1em;
+ padding-right: 0.5em;
}
#lists > li {
margin: 0.5em 0 0 0;
@@ -45,8 +55,9 @@ body[dir=rtl] #lists {
display: none;
}
li.listEntry {
+ line-height: 150%;
margin: 0 auto 0 auto;
- margin-left: 3em;
+ margin-left: 2.5em;
margin-right: 0em;
text-indent: -2em;
}
@@ -55,26 +66,51 @@ body[dir=rtl] li.listEntry {
margin-right: 1em;
}
li.listEntry > * {
+ margin-right: 0.5em;
text-indent: 0;
unicode-bidi: embed;
}
-li.listEntry > a:nth-of-type(2) {
- font-size: 16px;
- opacity: 0.7;
+li.listEntry.toRemove > input[type="checkbox"] {
+ visibility: hidden;
}
-li.listEntry > a:nth-of-type(2),
-li.listEntry > a:nth-of-type(2):visited {
- color: mediumblue;
+li.listEntry.toRemove > a.content {
+ text-decoration: line-through;
}
-li.listEntry > a:nth-of-type(2):hover {
+li.listEntry > .fa {
+ color: inherit;
+ display: none;
+ font-size: 110%;
+ opacity: 0.5;
+ vertical-align: baseline;
+ }
+li.listEntry > a.fa:hover {
opacity: 1;
}
-li.listEntry > a:nth-of-type(3) {
- font-size: smaller;
- opacity: 0.5;
+li.listEntry.support > a.support {
+ display: inline-block;
}
+li.listEntry > a.remove,
+li.listEntry > a.remove:visited {
+ color: darkred;
+ }
+li.listEntry.external > a.remove {
+ display: inline-block;
+ }
+li.listEntry.mustread > a.mustread {
+ display: inline-block;
+ }
+li.listEntry.mustread > a.mustread:hover {
+ color: mediumblue;
+ }
+li.listEntry > .counts {
+ display: none;
+ font-size: smaller;
+}
+li.listEntry > input[type="checkbox"]:checked ~ .counts {
+ display: inline;
+}
.dim {
- opacity: 0.5;
+ opacity: 0.6;
}
#buttonApply {
display: initial;
@@ -89,42 +125,42 @@ body[dir=rtl] #buttonApply {
#buttonApply.disabled {
display: none;
}
-span.status {
- border: 1px solid transparent;
+li.listEntry span.status {
color: #444;
+ cursor: default;
display: none;
- font-size: smaller;
- line-height: 1;
- margin: 0 0 0 0.5em;
- opacity: 0.8;
- padding: 1px 2px;
}
-span.unsecure {
- background-color: hsl(0, 100%, 88%);
- border-color: hsl(0, 100%, 83%);
- }
-li.listEntry.unsecure span.unsecure {
- display: inline;
- }
-span.obsolete {
- background-color: hsl(36, 100%, 80%);
- border-color: hsl(36, 100%, 75%);
- }
-li.listEntry.obsolete > input[type="checkbox"]:checked ~ span.obsolete {
- display: inline;
- }
-span.purge {
- border-color: #ddd;
- background-color: #eee;
- cursor: pointer;
- }
-span.purge:hover {
+li.listEntry span.status:hover {
opacity: 1;
}
-li.listEntry.cached span.purge {
- display: inline;
+li.listEntry span.status.fa {
+ /* font-size: 110%; */
}
-span.updating {
+li.listEntry span.unsecure {
+ color: darkred;
+ }
+li.listEntry.unsecure > input[type="checkbox"]:checked ~ span.unsecure {
+ display: inline-block;
+ }
+li.listEntry span.failed {
+ color: darkred;
+ }
+li.listEntry.failed span.failed {
+ display: inline-block;
+ }
+li.listEntry span.cache {
+ cursor: pointer;
+ }
+li.listEntry.cached:not(.updating):not(.obsolete) > input[type="checkbox"]:checked ~ span.cache {
+ display: inline-block;
+ }
+li.listEntry span.obsolete {
+ color: hsl(36, 100%, 40%);
+ }
+li.listEntry.obsolete:not(.updating) > input[type="checkbox"]:checked ~ span.obsolete {
+ display: inline-block;
+ }
+li.listEntry span.updating {
border: none;
padding: 0;
}
@@ -133,14 +169,15 @@ li.listEntry.updating span.updating {
display: inline-block;
}
#externalListsDiv {
- margin: 2em auto 0 2em;
+ margin: 1.5em auto 0 1.5em;
}
body[dir=rtl] #externalListsDiv {
- margin: 2em 2em 0;
+ margin: 1.5em 1.5em 0 auto;
}
#externalLists {
box-sizing: border-box;
- height: 10em;
+ height: 8em;
+ margin-top: 0.25em;
white-space: pre;
width: 100%;
word-wrap: normal;
diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js
index bd035874c..1b52d5276 100644
--- a/src/js/3p-filters.js
+++ b/src/js/3p-filters.js
@@ -31,7 +31,7 @@
var listDetails = {},
filteringSettingsHash = '',
- externalLists = '';
+ lastUpdateTemplateString = vAPI.i18n('3pLastUpdate');
/******************************************************************************/
@@ -41,10 +41,6 @@ var onMessage = function(msg) {
updateAssetStatus(msg);
break;
case 'staticFilteringDataChanged':
- filteringSettingsHash = [
- msg.parseCosmeticFilters,
- msg.ignoreGenericCosmeticFilters
- ].concat(msg.listKeys.sort()).join();
renderFilterLists();
break;
default:
@@ -63,12 +59,12 @@ var renderNumber = function(value) {
/******************************************************************************/
-var renderFilterLists = function(first) {
+var renderFilterLists = function(soft) {
var listGroupTemplate = uDom('#templates .groupEntry'),
listEntryTemplate = uDom('#templates .listEntry'),
listStatsTemplate = vAPI.i18n('3pListsOfBlockedHostsPerListStats'),
renderElapsedTimeToString = vAPI.i18n.renderElapsedTimeToString,
- lastUpdateString = vAPI.i18n('3pLastUpdate');
+ hideUnusedLists = document.body.classList.contains('hideUnused');
// Assemble a pretty list name if possible
var listNameFromListKey = function(listKey) {
@@ -92,46 +88,55 @@ var renderFilterLists = function(first) {
elem.setAttribute('href', 'asset-viewer.html?url=' + encodeURI(listKey));
elem.setAttribute('type', 'text/html');
elem.textContent = listNameFromListKey(listKey) + '\u200E';
- elem = li.querySelector('a:nth-of-type(2)');
- if ( entry.instructionURL ) {
- elem.setAttribute('href', entry.instructionURL);
- elem.style.setProperty('display', '');
- } else {
- elem.style.setProperty('display', 'none');
- }
- elem = li.querySelector('a:nth-of-type(3)');
if ( entry.supportName ) {
+ li.classList.add('support');
+ elem = li.querySelector('a.support');
elem.setAttribute('href', entry.supportURL);
- elem.textContent = '(' + entry.supportName + ')';
- elem.style.setProperty('display', '');
+ elem.setAttribute('title', entry.supportName);
} else {
- elem.style.setProperty('display', 'none');
+ li.classList.remove('support');
+ }
+ if ( entry.external ) {
+ li.classList.add('external');
+ } else {
+ li.classList.remove('external');
+ }
+ if ( entry.instructionURL ) {
+ li.classList.add('mustread');
+ elem = li.querySelector('a.mustread');
+ elem.setAttribute('href', entry.instructionURL);
+ } else {
+ li.classList.remove('mustread');
}
}
+ // https://github.com/gorhill/uBlock/issues/1429
+ if ( !soft ) {
+ elem = li.querySelector('input[type="checkbox"]');
+ elem.checked = entry.off !== true;
+ }
+ li.style.setProperty('display', hideUnusedLists && entry.off === true ? 'none' : '');
elem = li.querySelector('span.counts');
- var text = listStatsTemplate
- .replace('{{used}}', renderNumber(!entry.off && !isNaN(+entry.entryUsedCount) ? entry.entryUsedCount : 0))
- .replace('{{total}}', !isNaN(+entry.entryCount) ? renderNumber(entry.entryCount) : '?');
+ var text = '';
+ if ( !isNaN(+entry.entryUsedCount) && !isNaN(+entry.entryCount) ) {
+ text = listStatsTemplate
+ .replace('{{used}}', renderNumber(entry.off ? 0 : entry.entryUsedCount))
+ .replace('{{total}}', renderNumber(entry.entryCount));
+ }
elem.textContent = text;
-
// https://github.com/chrisaljoudi/uBlock/issues/104
var asset = listDetails.cache[listKey] || {};
-
- // https://github.com/gorhill/uBlock/issues/78
- // Badge for non-secure connection
var remoteURL = asset.remoteURL;
li.classList.toggle(
'unsecure',
typeof remoteURL === 'string' && remoteURL.lastIndexOf('http:', 0) === 0
);
- // Badge for update status
- li.classList.toggle('obsolete', entry.off !== true && asset.obsolete === true);
- // Badge for cache status
+ li.classList.toggle('failed', asset.error !== undefined);
+ li.classList.toggle('obsolete', asset.obsolete === true);
li.classList.toggle('cached', asset.cached === true && asset.writeTime > 0);
if ( asset.cached ) {
- li.querySelector('.status.purge').setAttribute(
+ li.querySelector('.status.cache').setAttribute(
'title',
- lastUpdateString.replace('{{ago}}', renderElapsedTimeToString(asset.writeTime))
+ lastUpdateTemplateString.replace('{{ago}}', renderElapsedTimeToString(asset.writeTime))
);
}
li.classList.remove('updating');
@@ -237,7 +242,6 @@ var renderFilterLists = function(first) {
}
uDom('#lists .listEntries .listEntry.discard').remove();
- uDom('#buttonUpdate').toggleClass('disabled', document.querySelector('#lists .listEntry.obsolete') === null);
uDom('#autoUpdate').prop('checked', listDetails.autoUpdate === true);
uDom('#listsOfBlockedHostsPrompt').text(
vAPI.i18n('3pListsOfBlockedHostsPrompt')
@@ -247,9 +251,9 @@ var renderFilterLists = function(first) {
// Compute a hash of the settings so that we can keep track of changes
// affecting the loading of filter lists.
- if ( first ) {
- uDom('#parseCosmeticFilters').prop('checked', listDetails.parseCosmeticFilters === true);
- uDom('#ignoreGenericCosmeticFilters').prop('checked', listDetails.ignoreGenericCosmeticFilters === true);
+ uDom('#parseCosmeticFilters').prop('checked', listDetails.parseCosmeticFilters === true);
+ uDom('#ignoreGenericCosmeticFilters').prop('checked', listDetails.ignoreGenericCosmeticFilters === true);
+ if ( !soft ) {
filteringSettingsHash = hashFromCurrentFromSettings();
}
renderWidgets();
@@ -265,16 +269,27 @@ var renderFilterLists = function(first) {
var renderWidgets = function() {
uDom('#buttonApply').toggleClass('disabled', filteringSettingsHash === hashFromCurrentFromSettings());
uDom('#buttonPurgeAll').toggleClass('disabled', document.querySelector('#lists .listEntry.cached') === null);
- uDom('#buttonUpdate').toggleClass('disabled', document.querySelector('#lists .listEntry.obsolete > input[type="checkbox"]:checked') === null);
+ uDom('#buttonUpdate').toggleClass('disabled', document.querySelector('#lists .listEntry.obsolete:not(.updating) > input[type="checkbox"]:checked') === null);
};
/******************************************************************************/
var updateAssetStatus = function(details) {
- var li = uDom('#lists .listEntry[data-listkey="' + details.key + '"]');
- li.toggleClass('obsolete', !details.cached);
- li.toggleClass('cached', details.cached);
- li.removeClass('updating');
+ var li = document.querySelector('#lists .listEntry[data-listkey="' + details.key + '"]');
+ if ( li === null ) { return; }
+ li.classList.toggle('failed', !!details.failed);
+ li.classList.toggle('obsolete', !details.cached);
+ li.classList.toggle('cached', !!details.cached);
+ li.classList.remove('updating');
+ if ( details.cached ) {
+ li.querySelector('.status.cache').setAttribute(
+ 'title',
+ lastUpdateTemplateString.replace(
+ '{{ago}}',
+ vAPI.i18n.renderElapsedTimeToString(Date.now())
+ )
+ );
+ }
renderWidgets();
};
@@ -291,7 +306,7 @@ var hashFromCurrentFromSettings = function() {
document.getElementById('ignoreGenericCosmeticFilters').checked
];
var listHash = [],
- listEntries = document.querySelectorAll('#lists .listEntry[data-listkey]'),
+ listEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)'),
liEntry,
i = listEntries.length;
while ( i-- ) {
@@ -300,7 +315,9 @@ var hashFromCurrentFromSettings = function() {
listHash.push(liEntry.getAttribute('data-listkey'));
}
}
- return hash.concat(listHash.sort()).join();
+ hash.push(listHash.sort().join());
+ hash.push(document.getElementById('externalLists').value.trim());
+ return hash.join();
};
/******************************************************************************/
@@ -311,6 +328,18 @@ var onFilteringSettingsChanged = function() {
/******************************************************************************/
+var onRemoveExternalList = function(ev) {
+ var liEntry = uDom(this).ancestors('[data-listkey]'),
+ listKey = liEntry.attr('data-listkey');
+ if ( listKey ) {
+ liEntry.toggleClass('toRemove');
+ renderWidgets();
+ }
+ ev.preventDefault();
+};
+
+/******************************************************************************/
+
var onPurgeClicked = function() {
var button = uDom(this),
liEntry = button.ancestors('[data-listkey]'),
@@ -322,13 +351,9 @@ var onPurgeClicked = function() {
// If the cached version is purged, the installed version must be assumed
// to be obsolete.
// https://github.com/gorhill/uBlock/issues/1733
- // An external filter list must not be marked as obsolete, they will always
- // be fetched anyways if there is no cached copy.
- var entry = listDetails.current && listDetails.current[listKey];
- if ( entry && entry.off !== true ) {
- liEntry.addClass('obsolete');
- uDom('#buttonUpdate').removeClass('disabled');
- }
+ // An external filter list must not be marked as obsolete, they will
+ // always be fetched anyways if there is no cached copy.
+ liEntry.addClass('obsolete');
liEntry.removeClass('cached');
if ( liEntry.descendants('input').first().prop('checked') ) {
@@ -351,24 +376,41 @@ var selectFilterLists = function(callback) {
value: document.getElementById('ignoreGenericCosmeticFilters').checked
});
- // Filter lists
- var listKeys = [],
- liEntries = document.querySelectorAll('#lists .listEntry[data-listkey]'),
+ // Filter lists to select
+ var toSelect = [],
+ liEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)'),
i = liEntries.length,
liEntry;
while ( i-- ) {
liEntry = liEntries[i];
if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) {
- listKeys.push(liEntry.getAttribute('data-listkey'));
+ toSelect.push(liEntry.getAttribute('data-listkey'));
}
}
+ // External filter lists to remove
+ var toRemove = [];
+ liEntries = document.querySelectorAll('#lists .listEntry.toRemove[data-listkey]');
+ i = liEntries.length;
+ while ( i-- ) {
+ toRemove.push(liEntries[i].getAttribute('data-listkey'));
+ }
+
+ // External filter lists to import
+ var externalListsElem = document.getElementById('externalLists'),
+ toImport = externalListsElem.value.trim();
+ externalListsElem.value = '';
+
messaging.send(
'dashboard',
- { what: 'selectFilterLists', keys: listKeys },
+ {
+ what: 'applyFilterListSelection',
+ toSelect: toSelect,
+ toImport: toImport,
+ toRemove: toRemove
+ },
callback
);
-
filteringSettingsHash = hashFromCurrentFromSettings();
};
@@ -387,8 +429,11 @@ var buttonApplyHandler = function() {
var buttonUpdateHandler = function() {
var onSelectionDone = function() {
- uDom('#lists .listEntry.obsolete').addClass('updating');
+ uDom('#lists .listEntry.obsolete > input[type="checkbox"]:checked')
+ .ancestors('.listEntry[data-listkey]')
+ .addClass('updating');
messaging.send('dashboard', { what: 'forceUpdateAssets' });
+ renderWidgets();
};
selectFilterLists(onSelectionDone);
renderWidgets();
@@ -404,7 +449,7 @@ var buttonPurgeAllHandler = function(ev) {
what: 'purgeAllCaches',
hard: ev.ctrlKey && ev.shiftKey
},
- renderFilterLists
+ function() { renderFilterLists(true); }
);
};
@@ -423,39 +468,13 @@ var autoUpdateCheckboxChanged = function() {
/******************************************************************************/
-var renderExternalLists = function() {
- var onReceived = function(details) {
- uDom('#externalLists').val(details);
- externalLists = details;
- };
- messaging.send(
- 'dashboard',
- { what: 'userSettings', name: 'externalLists' },
- onReceived
- );
-};
-
-/******************************************************************************/
-
-var externalListsChangeHandler = function() {
- uDom.nodeFromId('externalListsApply').disabled =
- uDom.nodeFromId('externalLists').value.trim() === externalLists.trim();
-};
-
-/******************************************************************************/
-
-var externalListsApplyHandler = function() {
- externalLists = uDom.nodeFromId('externalLists').value;
- messaging.send(
- 'dashboard',
- {
- what: 'userSettings',
- name: 'externalLists',
- value: externalLists
- }
- );
- renderFilterLists();
- uDom('#externalListsApply').prop('disabled', true);
+var toggleUnusedLists = function() {
+ document.body.classList.toggle('hideUnused');
+ var hide = document.body.classList.contains('hideUnused');
+ uDom('#lists li.listEntry > input[type="checkbox"]:not(:checked)')
+ .ancestors('li.listEntry[data-listkey]')
+ .css('display', hide ? 'none' : '');
+ vAPI.localStorage.setItem('hideUnusedFilterLists', hide ? '1' : '0');
};
/******************************************************************************/
@@ -478,7 +497,7 @@ var toCloudData = function() {
parseCosmeticFilters: uDom.nodeFromId('parseCosmeticFilters').checked,
ignoreGenericCosmeticFilters: uDom.nodeFromId('ignoreGenericCosmeticFilters').checked,
selectedLists: [],
- externalLists: externalLists
+ externalLists: listDetails.externalLists
};
var liEntries = uDom('#lists .listEntry'), liEntry;
@@ -529,7 +548,6 @@ var fromCloudData = function(data, append) {
elem.value += data.externalLists || '';
renderWidgets();
- externalListsChangeHandler();
};
self.cloud.onPush = toCloudData;
@@ -537,20 +555,25 @@ self.cloud.onPull = fromCloudData;
/******************************************************************************/
+document.body.classList.toggle(
+ 'hideUnused',
+ vAPI.localStorage.getItem('hideUnusedFilterLists') === '1'
+);
+
uDom('#autoUpdate').on('change', autoUpdateCheckboxChanged);
uDom('#parseCosmeticFilters').on('change', onFilteringSettingsChanged);
uDom('#ignoreGenericCosmeticFilters').on('change', onFilteringSettingsChanged);
uDom('#buttonApply').on('click', buttonApplyHandler);
uDom('#buttonUpdate').on('click', buttonUpdateHandler);
uDom('#buttonPurgeAll').on('click', buttonPurgeAllHandler);
-uDom('#lists').on('change', '.listEntry > input', onFilteringSettingsChanged);
-uDom('#lists').on('click', 'span.purge', onPurgeClicked);
-uDom('#externalLists').on('input', externalListsChangeHandler);
-uDom('#externalListsApply').on('click', externalListsApplyHandler);
+uDom('#listsOfBlockedHostsPrompt').on('click', toggleUnusedLists);
uDom('#lists').on('click', '.groupEntry > span', groupEntryClickHandler);
+uDom('#lists').on('change', '.listEntry > input', onFilteringSettingsChanged);
+uDom('#lists').on('click', '.listEntry > a.remove', onRemoveExternalList);
+uDom('#lists').on('click', 'span.cache', onPurgeClicked);
+uDom('#externalLists').on('input', onFilteringSettingsChanged);
-renderFilterLists(true);
-renderExternalLists();
+renderFilterLists();
/******************************************************************************/
diff --git a/src/js/assets.js b/src/js/assets.js
index 20f38aded..9a29812d7 100644
--- a/src/js/assets.js
+++ b/src/js/assets.js
@@ -96,7 +96,7 @@ var getTextFileFromURL = function(url, onLoad, onError) {
var onErrorReceived = function() {
this.onload = this.onerror = this.ontimeout = null;
- µBlock.logger.writeOne('', 'error', errorCantConnectTo.replace('{{msg}}', ''));
+ µBlock.logger.writeOne('', 'error', errorCantConnectTo.replace('{{msg}}', url));
onError.call(this);
};
@@ -805,7 +805,11 @@ var getRemote = function(assetKey, callback) {
};
var onRemoteContentError = function() {
- registerAssetSource(assetKey, { error: { time: Date.now(), error: this.statusText } });
+ var text = this.statusText;
+ if ( this.status === 0 ) {
+ text = 'network error';
+ }
+ registerAssetSource(assetKey, { error: { time: Date.now(), error: text } });
tryLoading();
};
@@ -948,6 +952,8 @@ var updateNext = function() {
if ( details.assetKey === 'assets.json' ) {
updateAssetSourceRegistry(details.content);
}
+ } else {
+ fireNotification('asset-update-failed', { assetKey: details.assetKey });
}
if ( findOne() !== undefined ) {
vAPI.setTimeout(updateNext, updaterAssetDelay);
diff --git a/src/js/background.js b/src/js/background.js
index 08c56ba50..5cc6d8915 100644
--- a/src/js/background.js
+++ b/src/js/background.js
@@ -127,6 +127,7 @@ return {
userFiltersPath: 'user-filters',
pslAssetKey: 'public_suffix_list.dat',
+ selectedFilterLists: [],
availableFilterLists: {},
selfieAfter: 23 * oneMinute,
diff --git a/src/js/messaging.js b/src/js/messaging.js
index 4cc466f7c..d5c780b69 100644
--- a/src/js/messaging.js
+++ b/src/js/messaging.js
@@ -94,10 +94,8 @@ var onMessage = function(request, sender, callback) {
var response;
switch ( request.what ) {
- case 'mouseClick':
- µb.mouseX = request.x;
- µb.mouseY = request.y;
- µb.mouseURL = request.url;
+ case 'applyFilterListSelection':
+ response = µb.applyFilterListSelection(request);
break;
case 'compileCosmeticFilterSelector':
@@ -147,6 +145,12 @@ var onMessage = function(request, sender, callback) {
µb.openNewTab(request.details);
break;
+ case 'mouseClick':
+ µb.mouseX = request.x;
+ µb.mouseY = request.y;
+ µb.mouseURL = request.url;
+ break;
+
case 'reloadTab':
if ( vAPI.isBehindTheSceneTabId(request.tabId) === false ) {
vAPI.tabs.reload(request.tabId);
@@ -160,10 +164,6 @@ var onMessage = function(request, sender, callback) {
µb.scriptlets.report(tabId, request.scriptlet, request.response);
break;
- case 'selectFilterLists':
- µb.saveSelectedFilterLists(request.keys, request.append);
- break;
-
case 'setWhitelist':
µb.netWhitelist = µb.whitelistFromString(request.whitelist);
µb.saveWhitelist();
@@ -873,11 +873,12 @@ var getLists = function(callback) {
autoUpdate: µb.userSettings.autoUpdate,
available: null,
cache: null,
- parseCosmeticFilters: µb.userSettings.parseAllABPHideFilters,
cosmeticFilterCount: µb.cosmeticFilteringEngine.getFilterCount(),
current: µb.availableFilterLists,
+ externalLists: µb.userSettings.externalLists,
ignoreGenericCosmeticFilters: µb.userSettings.ignoreGenericCosmeticFilters,
netFilterCount: µb.staticNetFilteringEngine.getFilterCount(),
+ parseCosmeticFilters: µb.userSettings.parseAllABPHideFilters,
userFiltersPath: µb.userFiltersPath,
aliases: µb.assets.listKeyAliases
};
@@ -1287,8 +1288,7 @@ var onMessage = function(request, sender, callback) {
case 'subscriberData':
response = {
- confirmStr: vAPI.i18n('subscriberConfirm'),
- externalLists: µBlock.userSettings.externalLists
+ confirmStr: vAPI.i18n('subscriberConfirm')
};
break;
diff --git a/src/js/scriptlets/subscriber.js b/src/js/scriptlets/subscriber.js
index 887e95db8..4250c5ff1 100644
--- a/src/js/scriptlets/subscriber.js
+++ b/src/js/scriptlets/subscriber.js
@@ -19,7 +19,7 @@
Home: https://github.com/gorhill/uBlock
*/
-/* global vAPI, HTMLDocument */
+/* global HTMLDocument */
'use strict';
@@ -79,9 +79,7 @@ var onAbpLinkClicked = function(ev) {
var matches = /^abp:\/*subscribe\/*\?location=([^&]+).*title=([^&]+)/.exec(href);
if ( matches === null ) {
matches = /^https?:\/\/.*?[&?]location=([^&]+).*?&title=([^&]+)/.exec(href);
- if ( matches === null ) {
- return;
- }
+ if ( matches === null ) { return; }
}
var location = decodeURIComponent(matches[1]);
@@ -95,44 +93,18 @@ var onAbpLinkClicked = function(ev) {
messaging.send('scriptlets', { what: 'reloadAllFilters' });
};
- var onExternalListsSaved = function() {
- messaging.send(
- 'scriptlets',
- {
- what: 'selectFilterLists',
- keys: [ location ],
- append: true
- },
- onListsSelectionDone
- );
- };
-
var onSubscriberDataReady = function(details) {
var confirmStr = details.confirmStr
.replace('{{url}}', location)
.replace('{{title}}', title);
- if ( !window.confirm(confirmStr) ) {
- return;
- }
-
- // List already subscribed to?
- // https://github.com/chrisaljoudi/uBlock/issues/1033
- // Split on line separators, not whitespaces.
- var text = details.externalLists.trim();
- var lines = text !== '' ? text.split(/\s*[\n\r]+\s*/) : [];
- if ( lines.indexOf(location) !== -1 ) {
- return;
- }
- lines.push(location, '');
-
+ if ( !window.confirm(confirmStr) ) { return; }
messaging.send(
'scriptlets',
{
- what: 'userSettings',
- name: 'externalLists',
- value: lines.join('\n')
+ what: 'applyFilterListSelection',
+ toImport: location
},
- onExternalListsSaved
+ onListsSelectionDone
);
};
diff --git a/src/js/storage.js b/src/js/storage.js
index 6b0711853..782d2da24 100644
--- a/src/js/storage.js
+++ b/src/js/storage.js
@@ -186,28 +186,33 @@
// Uncomment when all have moved to v1.11 and beyond.
//vAPI.storage.remove('remoteBlacklists');
}
+ µb.selectedFilterLists = listKeys.slice();
callback(listKeys);
});
};
-µBlock.saveSelectedFilterLists = function(listKeys, append) {
+µBlock.saveSelectedFilterLists = function(newKeys, append) {
var µb = this;
- var save = function(keys) {
- var uniqueKeys = µb.setToArray(new Set(keys));
+ this.loadSelectedFilterLists(function(oldKeys) {
+ oldKeys = oldKeys || [];
+ if ( append ) {
+ newKeys = newKeys.concat(oldKeys);
+ }
+ var newSet = new Set(newKeys);
+ // Purge unused filter lists from cache.
+ for ( var i = 0, n = oldKeys.length; i < n; i++ ) {
+ if ( newSet.has(oldKeys[i]) === false ) {
+ µb.removeFilterList(oldKeys[i]);
+ }
+ }
+ newKeys = µb.setToArray(newSet);
var bin = {
- selectedFilterLists: uniqueKeys,
- remoteBlacklists: µb.oldDataFromNewListKeys(uniqueKeys)
+ selectedFilterLists: newKeys,
+ remoteBlacklists: µb.oldDataFromNewListKeys(newKeys)
};
+ µb.selectedFilterLists = newKeys;
vAPI.storage.set(bin);
- };
- if ( append ) {
- this.loadSelectedFilterLists(function(keys) {
- listKeys = listKeys.concat(keys || []);
- save(listKeys);
- });
- } else {
- save(listKeys);
- }
+ });
};
// TODO(seamless migration):
@@ -262,6 +267,113 @@
/******************************************************************************/
+µBlock.applyFilterListSelection = function(details, callback) {
+ var µb = this,
+ selectedListKeySet = new Set(this.selectedFilterLists),
+ externalLists = this.userSettings.externalLists,
+ i, n, assetKey;
+
+ // Filter lists to select
+ if ( Array.isArray(details.toSelect) ) {
+ if ( details.merge ) {
+ for ( i = 0, n = details.toSelect.length; i < n; i++ ) {
+ selectedListKeySet.add(details.toSelect[i]);
+ }
+ } else {
+ selectedListKeySet = new Set(details.toSelect);
+ }
+ }
+
+ // Imported filter lists to remove
+ if ( Array.isArray(details.toRemove) ) {
+ var removeURLFromHaystack = function(haystack, needle) {
+ return haystack.replace(
+ new RegExp(
+ '(^|\\n)' +
+ needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +
+ '(\\n|$)', 'g'),
+ '\n'
+ ).trim();
+ };
+ for ( i = 0, n = details.toRemove.length; i < n; i++ ) {
+ assetKey = details.toRemove[i];
+ selectedListKeySet.delete(assetKey);
+ externalLists = removeURLFromHaystack(externalLists, assetKey);
+ this.removeFilterList(assetKey);
+ }
+ }
+
+ // Filter lists to import
+ if ( typeof details.toImport === 'string' ) {
+ // https://github.com/gorhill/uBlock/issues/1181
+ // Try mapping the URL of an imported filter list to the assetKey of an
+ // existing stock list.
+ var assetKeyFromURL = function(url) {
+ var needle = url.replace(/^https?:/, '');
+ var assets = µb.availableFilterLists, asset;
+ for ( var assetKey in assets ) {
+ asset = assets[assetKey];
+ if ( asset.content !== 'filters' ) { continue; }
+ if ( typeof asset.contentURL === 'string' ) {
+ if ( asset.contentURL.endsWith(needle) ) { return assetKey; }
+ continue;
+ }
+ if ( Array.isArray(asset.contentURL) === false ) { continue; }
+ for ( i = 0, n = asset.contentURL.length; i < n; i++ ) {
+ if ( asset.contentURL[i].endsWith(needle) ) {
+ return assetKey;
+ }
+ }
+ }
+ return url;
+ };
+ var importedSet = new Set(this.listKeysFromCustomFilterLists(externalLists)),
+ toImportSet = new Set(this.listKeysFromCustomFilterLists(details.toImport)),
+ iter = toImportSet.values();
+ for (;;) {
+ var entry = iter.next();
+ if ( entry.done ) { break; }
+ if ( importedSet.has(entry.value) ) { continue; }
+ assetKey = assetKeyFromURL(entry.value);
+ if ( assetKey === entry.value ) {
+ importedSet.add(entry.value);
+ }
+ selectedListKeySet.add(assetKey);
+ }
+ externalLists = this.setToArray(importedSet).sort().join('\n');
+ }
+
+ var result = this.setToArray(selectedListKeySet);
+ if ( externalLists !== this.userSettings.externalLists ) {
+ this.userSettings.externalLists = externalLists;
+ vAPI.storage.set({ externalLists: externalLists });
+ }
+ this.saveSelectedFilterLists(result);
+ if ( typeof callback === 'function' ) {
+ callback(result);
+ }
+};
+
+/******************************************************************************/
+
+µBlock.listKeysFromCustomFilterLists = function(raw) {
+ var out = new Set(),
+ reIgnore = /^[!#]/,
+ reValid = /^[a-z-]+:\/\/\S+/,
+ lineIter = new this.LineIterator(raw),
+ location;
+ while ( lineIter.eot() === false ) {
+ location = lineIter.next().trim();
+ if ( reIgnore.test(location) || !reValid.test(location) ) {
+ continue;
+ }
+ out.add(location);
+ }
+ return this.setToArray(out);
+};
+
+/******************************************************************************/
+
µBlock.saveUserFilters = function(content, callback) {
// https://github.com/gorhill/uBlock/issues/1022
// Be sure to end with an empty line.
@@ -315,21 +427,6 @@
/******************************************************************************/
-µBlock.listKeysFromCustomFilterLists = function(raw) {
- var out = {};
- var reIgnore = /^[!#]|[^0-9A-Za-z!*'();:@&=+$,\/?%#\[\]_.~-]/,
- lineIter = new this.LineIterator(raw),
- location;
- while ( lineIter.eot() === false ) {
- location = lineIter.next().trim();
- if ( location === '' || reIgnore.test(location) ) { continue; }
- out[location] = true;
- }
- return Object.keys(out);
-};
-
-/******************************************************************************/
-
µBlock.autoSelectRegionalFilterLists = function(lists) {
var lang = self.navigator.language.slice(0, 2),
selectedListKeys = [],
@@ -351,41 +448,6 @@
/******************************************************************************/
-µBlock.changeExternalFilterLists = function(before, after) {
- var µb = µBlock;
- var onLoaded = function(keys) {
- var fullDict = new Set(keys || []),
- mustSave = false,
- oldKeys = µb.listKeysFromCustomFilterLists(before),
- oldDict = new Set(oldKeys),
- newKeys = µb.listKeysFromCustomFilterLists(after),
- newDict = new Set(newKeys),
- i, key;
- i = oldKeys.length;
- while ( i-- ) {
- key = oldKeys[i];
- if ( fullDict.has(key) && !newDict.has(key) ) {
- fullDict.delete(key);
- mustSave = true;
- }
- }
- i = newKeys.length;
- while ( i-- ) {
- key = newKeys[i];
- if ( !fullDict.has(key) && !oldDict.has(key) ) {
- fullDict.add(key);
- mustSave = true;
- }
- }
- if ( mustSave ) {
- µb.saveSelectedFilterLists(µb.setToArray(fullDict));
- }
- };
- this.loadSelectedFilterLists(onLoaded);
-};
-
-/******************************************************************************/
-
µBlock.getAvailableLists = function(callback) {
var µb = this,
oldAvailableLists = {},
@@ -1006,7 +1068,7 @@
if ( topic === 'before-asset-updated' ) {
if (
this.availableFilterLists.hasOwnProperty(details.assetKey) &&
- this.availableFilterLists[details.assetKey].off === true
+ this.selectedFilterLists.indexOf(details.assetKey) === -1
) {
return false;
}
@@ -1018,7 +1080,7 @@
var cached = typeof details.content === 'string' && details.content !== '';
if ( this.availableFilterLists.hasOwnProperty(details.assetKey) ) {
if ( cached ) {
- if ( this.availableFilterLists[details.assetKey].off !== true ) {
+ if ( this.selectedFilterLists.indexOf(details.assetKey) !== -1 ) {
this.extractFilterListMetadata(
details.assetKey,
details.content
@@ -1049,6 +1111,16 @@
return;
}
+ // Update failed.
+ if ( topic === 'asset-update-failed' ) {
+ vAPI.messaging.broadcast({
+ what: 'assetUpdated',
+ key: details.assetKey,
+ failed: true
+ });
+ return;
+ }
+
// Reload all filter lists if needed.
if ( topic === 'after-assets-updated' ) {
if ( details.assetKeys.length !== 0 ) {
diff --git a/src/js/ublock.js b/src/js/ublock.js
index f765fbd74..18c61e5cd 100644
--- a/src/js/ublock.js
+++ b/src/js/ublock.js
@@ -317,9 +317,6 @@ var reInvalidHostname = /[^a-z0-9.\-\[\]:]/,
// Pre-change
switch ( name ) {
- case 'externalLists':
- this.changeExternalFilterLists(us.externalLists, value);
- break;
case 'largeMediaSize':
if ( typeof value !== 'number' ) {
value = parseInt(value, 10) || 0;