diff --git a/3p-filters.html b/3p-filters.html
index 43c2ea2aa..ff9cce186 100644
--- a/3p-filters.html
+++ b/3p-filters.html
@@ -12,16 +12,23 @@ div > p:first-child {
div > p:last-child {
margin-bottom: 0;
}
+body > ul {
+ margin: 0.5em 0 0 0;
+ padding-left: 1em;
+ }
ul {
padding: 0;
list-style-type: none;
}
ul > li {
- margin: 0.5em 0 3px 0;
+ margin: 0.5em 0 0 0;
padding: 0;
font-size: 15px;
list-style-type: none;
}
+ul > li > ul {
+ margin: 0.25em 0 0 0;
+ }
ul > li > ul > li {
font-size: 14px;
margin: 0 0 0 1em;
@@ -35,12 +42,18 @@ ul > li > ul > li {
height: 16em;
white-space: nowrap;
}
+#externalLists {
+ font-size: smaller;
+ width: 48em;
+ height: 8em;
+ white-space: nowrap;
+ }
-
+
-
+
+
+
diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 95e342214..543cd8dc7 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -131,6 +131,18 @@
"message":"Regions, languages",
"description":"English: Regions, languages"
},
+ "3pGroupCustom":{
+ "message":"Custom",
+ "description":"English: Custom"
+ },
+ "3pExternalListsHint":{
+ "message":"One URL per line. Lines prefixed with ‘!’ will be ignored. Invalid URLs will be silently ignored.",
+ "description":"English: One URL per line. Lines prefixed with ‘!’ will be ignored. Invalid URLs will be silently ignored."
+ },
+ "3pExternalListsApply":{
+ "message":"Apply",
+ "description":"English: Apply"
+ },
"1pFormatHint":{
"message":"One filter per line. A filter can be a plain hostname, or an Adblock Plus-compatible filter. Lines prefixed with ‘!’ will be ignored.",
"description":"English: One filter per line. A filter can be a plain hostname, or an Adblock Plus-compatible filter. Lines prefixed with ‘!’ will be ignored."
diff --git a/assets/ublock/filters.txt b/assets/ublock/filters.txt
index ee23b7c73..69621cfa9 100644
--- a/assets/ublock/filters.txt
+++ b/assets/ublock/filters.txt
@@ -24,6 +24,7 @@
www.zerohedge.com##.similar-box
# https://github.com/gorhill/uBlock/issues/57
+# https://github.com/gorhill/uBlock/issues/98
# New filter class: entity filters, where
# entity = domain minus public suffix
# The following filters were taken out of EasyList and given an entity name,
diff --git a/css/dashboard-common.css b/css/dashboard-common.css
index 81c59b7da..705e35346 100644
--- a/css/dashboard-common.css
+++ b/css/dashboard-common.css
@@ -48,7 +48,7 @@ a {
display: none;
border: 1px dotted black;
background-color: #F8F8F8;
- font-size: 90%;
+ font-size: 13px;
}
.whatisthis-expandable > p {
margin-top: 1em;
diff --git a/js/3p-filters.js b/js/3p-filters.js
index 418af8179..3d5f998c4 100644
--- a/js/3p-filters.js
+++ b/js/3p-filters.js
@@ -28,12 +28,13 @@
/******************************************************************************/
var userListName = chrome.i18n.getMessage('1pPageName');
-var selectedBlacklistsHash = '';
-var listURLPrefix = 'asset-viewer.html?url=';
+var listDetails = {};
+var parseCosmeticFilters = true;
+var externalLists = '';
/******************************************************************************/
-messaging.start('lists.js');
+messaging.start('3p-filters.js');
var onMessage = function(msg) {
switch ( msg.what ) {
@@ -67,8 +68,6 @@ var renderNumber = function(value) {
var renderBlacklists = function() {
// empty list first
- uDom('#lists .listDetails').remove();
-
var µb = getµb();
uDom('#listsOfBlockedHostsPrompt').text(
@@ -111,19 +110,22 @@ var renderBlacklists = function() {
var listStatsTemplate = chrome.i18n.getMessage('3pListsOfBlockedHostsPerListStats');
var htmlFromBranch = function(groupKey, listKeys, lists) {
- listKeys.sort(function(a, b) {
- return lists[a].title.localeCompare(lists[b].title);
- });
var html = [
'',
chrome.i18n.getMessage('3pGroup' + groupKey.charAt(0).toUpperCase() + groupKey.slice(1)),
''
];
+ if ( !listKeys ) {
+ return html.join('');
+ }
+ listKeys.sort(function(a, b) {
+ return lists[a].title.localeCompare(lists[b].title);
+ });
var listEntryTemplate = [
'',
' ',
' ',
- '',
+ ' ',
'{{name}}',
' ',
': ',
@@ -140,7 +142,7 @@ var renderBlacklists = function() {
.replace('{{URL}}', encodeURI(listKey))
.replace('{{name}}', htmlFromListName(list.title, listKey))
.replace('{{used}}', !list.off && !isNaN(+list.entryUsedCount) ? renderNumber(list.entryUsedCount) : '0')
- .replace('{{total}}', !isNaN(+list.entryCount) ? renderNumber(list.entryCount) : '?')
+ .replace('{{total}}', !isNaN(+list.entryCount) ? renderNumber(list.entryCount) : '?');
html.push(listEntry);
}
html.push(' ');
@@ -164,58 +166,84 @@ var renderBlacklists = function() {
return groups;
};
- var html = [];
- var groups = groupsFromLists(µb.remoteBlacklists);
- var groupKey;
- var groupKeys = [
- 'default',
- 'ads',
- 'privacy',
- 'malware',
- 'social',
- 'multipurpose',
- 'regions'
- ];
- for ( var i = 0; i < groupKeys.length; i++ ) {
- groupKey = groupKeys[i];
- html.push(htmlFromBranch(groupKey, groups[groupKey], µb.remoteBlacklists));
- delete groups[groupKey];
- }
- // For all groups not covered above (if any left)
- groupKeys = Object.keys(groups);
- for ( var i = 0; i < groupKeys.length; i++ ) {
- groupKey = groupKeys[i];
- html.push(htmlFromBranch(groupKey, groups[groupKey], µb.remoteBlacklists));
- delete groups[groupKey];
- }
+ var onListsReceived = function(details) {
+ listDetails = details;
- uDom('#lists').html(html.join(''));
- uDom('#parseAllABPHideFilters').prop('checked', µb.userSettings.parseAllABPHideFilters === true);
- uDom('#ubiquitousParseAllABPHideFiltersPrompt2').text(
- chrome.i18n.getMessage("listsParseAllABPHideFiltersPrompt2")
- .replace('{{abpHideFilterCount}}', renderNumber(µb.abpHideFilters.getFilterCount()))
- );
+ var lists = details.available;
+ var html = [];
+ var groups = groupsFromLists(lists);
+ var groupKey, i;
+ var groupKeys = [
+ 'default',
+ 'ads',
+ 'privacy',
+ 'malware',
+ 'social',
+ 'multipurpose',
+ 'regions',
+ 'custom'
+ ];
+ for ( i = 0; i < groupKeys.length; i++ ) {
+ groupKey = groupKeys[i];
+ html.push(htmlFromBranch(groupKey, groups[groupKey], lists));
+ delete groups[groupKey];
+ }
+ // For all groups not covered above (if any left)
+ groupKeys = Object.keys(groups);
+ for ( i = 0; i < groupKeys.length; i++ ) {
+ groupKey = groupKeys[i];
+ html.push(htmlFromBranch(groupKey, groups[groupKey], lists));
+ delete groups[groupKey];
+ }
- uDom('a').attr('target', '_blank');
- selectedBlacklistsHash = getSelectedBlacklistsHash();
+ uDom('#lists .listDetails').remove();
+ uDom('#lists').html(html.join(''));
+ uDom('#parseAllABPHideFilters').prop('checked', listDetails.cosmetic === true);
+ uDom('#ubiquitousParseAllABPHideFiltersPrompt2').text(
+ chrome.i18n.getMessage("listsParseAllABPHideFiltersPrompt2")
+ .replace('{{abpHideFilterCount}}', renderNumber(µb.abpHideFilters.getFilterCount()))
+ );
+ uDom('a').attr('target', '_blank');
+ selectedBlacklistsChanged();
+ };
+
+ messaging.ask({ what: 'getLists' }, onListsReceived);
};
/******************************************************************************/
-// Create a hash so that we know whether the selection of preset blacklists
-// has changed.
+// Check whether lists need reloading.
-var getSelectedBlacklistsHash = function() {
- var hash = '';
- var inputs = uDom('#lists .listDetails > input');
- var i = inputs.length();
- while ( i-- ) {
- hash += inputs.subset(i).prop('checked').toString();
+var needToReload = function() {
+ if ( listDetails.cosmetic !== getµb().userSettings.parseAllABPHideFilters ) {
+ return true;
}
- // Factor in whether cosmetic filters are to be processed
- hash += uDom('#parseAllABPHideFilters').prop('checked').toString();
-
- return hash;
+ var availableLists = listDetails.available;
+ var currentLists = listDetails.current;
+ var location, availableOff, currentOff;
+ // This check existing entries
+ for ( location in availableLists ) {
+ if ( availableLists.hasOwnProperty(location) === false ) {
+ continue;
+ }
+ availableOff = availableLists[location].off === true;
+ currentOff = currentLists[location] === undefined || currentLists[location].off === true;
+ if ( availableOff !== currentOff ) {
+ return true;
+ }
+ }
+ // This check removed entries
+ for ( location in currentLists ) {
+ if ( currentLists.hasOwnProperty(location) === false ) {
+ continue;
+ }
+ currentOff = currentLists[location].off === true;
+ availableOff = availableLists[location] === undefined || availableLists[location].off === true;
+ if ( availableOff !== currentOff ) {
+ return true;
+ }
+ }
+ return false;
};
/******************************************************************************/
@@ -223,10 +251,21 @@ var getSelectedBlacklistsHash = function() {
// This is to give a visual hint that the selection of blacklists has changed.
var selectedBlacklistsChanged = function() {
- uDom('#blacklistsApply').prop(
- 'disabled',
- getSelectedBlacklistsHash() === selectedBlacklistsHash
- );
+ uDom('#blacklistsApply').prop('disabled', !needToReload());
+};
+
+/******************************************************************************/
+
+var onListCheckboxChanged = function() {
+ var href = uDom(this).parent().find('a').first().attr('href');
+ if ( typeof href !== 'string' ) {
+ return;
+ }
+ if ( listDetails.available[href] === undefined ) {
+ return;
+ }
+ listDetails.available[href].off = !this.checked;
+ selectedBlacklistsChanged();
};
/******************************************************************************/
@@ -242,11 +281,16 @@ var onListLinkClicked = function(ev) {
/******************************************************************************/
var blacklistsApplyHandler = function() {
- var newHash = getSelectedBlacklistsHash();
- if ( newHash === selectedBlacklistsHash ) {
+ if ( !needToReload() ) {
return;
}
// Reload blacklists
+ messaging.tell({
+ what: 'userSettings',
+ name: 'parseAllABPHideFilters',
+ value: parseCosmeticFilters
+ });
+ // Reload blacklists
var switches = [];
var lis = uDom('#lists .listDetails');
var i = lis.length();
@@ -255,8 +299,7 @@ var blacklistsApplyHandler = function() {
path = lis
.subset(i)
.find('a')
- .attr('href')
- .replace(listURLPrefix, '');
+ .attr('href');
switches.push({
location: path,
off: lis.subset(i).find('input').prop('checked') === false
@@ -266,30 +309,61 @@ var blacklistsApplyHandler = function() {
what: 'reloadAllFilters',
switches: switches
});
- uDom('#blacklistsApply').prop('disabled', true );
+ uDom('#blacklistsApply').prop('disabled', true);
};
/******************************************************************************/
var abpHideFiltersCheckboxChanged = function() {
+ listDetails.cosmetic = this.checked;
+ selectedBlacklistsChanged();
+};
+
+/******************************************************************************/
+
+var renderExternalLists = function() {
+ var onReceived = function(details) {
+ uDom('#externalLists').val(details);
+ externalLists = details;
+ };
+ messaging.ask({ what: 'userSettings', name: 'externalLists' }, onReceived);
+};
+
+/******************************************************************************/
+
+var externalListsChangeHandler = function() {
+ uDom('#externalListsApply').prop(
+ 'disabled',
+ this.value.trim() === externalLists
+ );
+};
+
+/******************************************************************************/
+
+var externalListsApplyHandler = function() {
+ externalLists = uDom('#externalLists').val();
messaging.tell({
what: 'userSettings',
- name: 'parseAllABPHideFilters',
- value: this.checked
+ name: 'externalLists',
+ value: externalLists
});
- selectedBlacklistsChanged();
+ renderBlacklists();
+ uDom('#externalListsApply').prop('disabled', true);
};
/******************************************************************************/
uDom.onLoad(function() {
// Handle user interaction
- uDom('#lists').on('change', '.listDetails', selectedBlacklistsChanged);
- uDom('#lists').on('click', '.listDetails > a:first-child', onListLinkClicked);
- uDom('#blacklistsApply').on('click', blacklistsApplyHandler);
uDom('#parseAllABPHideFilters').on('change', abpHideFiltersCheckboxChanged);
+ uDom('#blacklistsApply').on('click', blacklistsApplyHandler);
+ uDom('#lists').on('change', '.listDetails > input', onListCheckboxChanged);
+ uDom('#lists').on('click', '.listDetails > a:nth-of-type(1)', onListLinkClicked);
+ uDom('#externalLists').on('input', externalListsChangeHandler);
+ uDom('#externalListsApply').on('click', externalListsApplyHandler);
renderBlacklists();
+ renderExternalLists();
});
/******************************************************************************/
diff --git a/js/asset-updater.js b/js/asset-updater.js
index 5ce68031c..546682bb6 100644
--- a/js/asset-updater.js
+++ b/js/asset-updater.js
@@ -122,7 +122,7 @@ var getUpdateList = function(callback) {
}
};
- µBlock.assets.getRemote('assets/checksums.txt', onRemoteChecksumsLoaded);
+ µBlock.assets.getRepo('assets/checksums.txt', onRemoteChecksumsLoaded);
µBlock.assets.get('assets/checksums.txt', onLocalChecksumsLoaded);
};
@@ -200,6 +200,9 @@ var update = function(list, callback) {
processList();
};
+ // Purge obsolete external assets
+ µBlock.assets.purge(/^https?:\/\/.+$/, Date.now() - µBlock.updateAssetsEvery);
+
if ( list ) {
processList();
} else {
diff --git a/js/assets.js b/js/assets.js
index 640230a12..997cbfa2a 100644
--- a/js/assets.js
+++ b/js/assets.js
@@ -146,7 +146,7 @@ var cachedAssetsManager = (function() {
};
var onEntries = function(entries) {
if ( entries[path] === undefined ) {
- entries[path] = true;
+ entries[path] = Date.now();
bin.cached_asset_entries = entries;
}
chrome.storage.local.set(bin, onSaved);
@@ -154,7 +154,7 @@ var cachedAssetsManager = (function() {
getEntries(onEntries);
};
- exports.remove = function(pattern) {
+ exports.remove = function(pattern, before) {
var onEntries = function(entries) {
var keystoRemove = [];
var paths = Object.keys(entries);
@@ -168,6 +168,9 @@ var cachedAssetsManager = (function() {
if ( pattern instanceof RegExp && !pattern.test(path) ) {
continue;
}
+ if ( typeof before === 'number' && entries[path] >= before ) {
+ continue;
+ }
keystoRemove.push(cachedAssetPathPrefix + path);
delete entries[path];
}
@@ -406,7 +409,9 @@ var readLocalFile = function(path, callback) {
/******************************************************************************/
-var readRemoteFile = function(path, callback) {
+// Get the repository copy of a built-in asset.
+
+var readRepoFile = function(path, callback) {
var reportBack = function(content, err) {
var details = {
'path': path,
@@ -416,8 +421,8 @@ var readRemoteFile = function(path, callback) {
callback(details);
};
- var onRemoteFileLoaded = function() {
- // console.log('µBlock> readRemoteFile() / onRemoteFileLoaded()');
+ var onRepoFileLoaded = function() {
+ // console.log('µBlock> readRepoFile() / onRepoFileLoaded()');
// https://github.com/gorhill/httpswitchboard/issues/263
if ( this.status === 200 ) {
reportBack(this.responseText);
@@ -427,8 +432,8 @@ var readRemoteFile = function(path, callback) {
this.onload = this.onerror = null;
};
- var onRemoteFileError = function(ev) {
- console.error('µBlock> readRemoteFile() / onRemoteFileError("%s")', path);
+ var onRepoFileError = function(ev) {
+ console.error('µBlock> readRepoFile() / onRepoFileError("%s")', path);
reportBack('', 'Error');
this.onload = this.onerror = null;
};
@@ -436,13 +441,64 @@ var readRemoteFile = function(path, callback) {
// 'ublock=...' is to skip browser cache
getTextFileFromURL(
repositoryRoot + path + '?ublock=' + Date.now(),
- onRemoteFileLoaded,
- onRemoteFileError
+ onRepoFileLoaded,
+ onRepoFileError
);
};
/******************************************************************************/
+var readExternalFile = function(path, callback) {
+ var reportBack = function(content, err) {
+ var details = {
+ 'path': path,
+ 'content': content
+ };
+ if ( err ) {
+ details.error = err;
+ }
+ callback(details);
+ };
+
+ var onExternalFileCached = function(details) {
+ this.onload = this.onerror = null;
+ // console.log('µBlock> onExternalFileCached()');
+ reportBack(details.content);
+ };
+
+ var onExternalFileLoaded = function() {
+ this.onload = this.onerror = null;
+ // console.log('µBlock> onExternalFileLoaded()');
+ cachedAssetsManager.save(path, this.responseText, onExternalFileCached);
+ };
+
+ var onExternalFileError = function(ev) {
+ console.error('µBlock> onExternalFileError() / onLocalFileError("%s")', path);
+ reportBack('', 'Error');
+ this.onload = this.onerror = null;
+ };
+
+ var onCachedContentLoaded = function(details) {
+ // console.log('µBlock> readLocalFile() / onCacheFileLoaded()');
+ reportBack(details.content);
+ };
+
+ var onCachedContentError = function(details) {
+ // console.error('µBlock> readLocalFile() / onCacheFileError("%s")', path);
+ getTextFileFromURL(details.path, onExternalFileLoaded, onExternalFileError);
+ };
+
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
+};
+
+/******************************************************************************/
+
+var purgeCache = function(pattern, before) {
+ cachedAssetsManager.remove(pattern, before);
+};
+
+/******************************************************************************/
+
var writeLocalFile = function(path, content, callback) {
cachedAssetsManager.save(path, content, callback);
};
@@ -580,7 +636,9 @@ synchronizeCache();
return {
'get': readLocalFile,
- 'getRemote': readRemoteFile,
+ 'getExternal': readExternalFile,
+ 'getRepo': readRepoFile,
+ 'purge': purgeCache,
'put': writeLocalFile,
'update': updateFromRemote
};
diff --git a/js/background.js b/js/background.js
index 8c8447f5f..5e9d45ffa 100644
--- a/js/background.js
+++ b/js/background.js
@@ -32,6 +32,7 @@ return {
userSettings: {
collapseBlocked: true,
+ externalLists: '',
logBlockedRequests: false,
logAllowedRequests: false,
parseAllABPHideFilters: true,
@@ -47,13 +48,12 @@ return {
projectServerRoot: 'https://raw.githubusercontent.com/gorhill/uBlock/master/',
userFiltersPath: 'assets/user/filters.txt',
- // list of remote blacklist locations
- remoteBlacklists: {
+ // permanent lists
+ permanentLists: {
// User
'assets/user/filters.txt': {
group: 'default'
},
-
// uBlock
'assets/ublock/filters.txt': {
title: 'µBlock filters',
@@ -64,7 +64,10 @@ return {
title: 'µBlock filters - Privacy',
group: 'default'
}
- // 3rd-party lists fetched dynamically
+ },
+
+ // current lists
+ remoteBlacklists: {
},
pageStores: {},
diff --git a/js/element-picker.js b/js/element-picker.js
index 43d13cccb..be05c0269 100644
--- a/js/element-picker.js
+++ b/js/element-picker.js
@@ -36,7 +36,7 @@
var InvalidCharacterError = function(message) {
this.message = message;
};
- InvalidCharacterError.prototype = new Error;
+ InvalidCharacterError.prototype = new Error();
InvalidCharacterError.prototype.name = 'InvalidCharacterError';
if (!CSS.escape) {
diff --git a/js/messaging-handlers.js b/js/messaging-handlers.js
index 64745fa00..ef2313ad4 100644
--- a/js/messaging-handlers.js
+++ b/js/messaging-handlers.js
@@ -233,11 +233,28 @@ var onMessage = function(request, sender, callback) {
(function() {
+var getLists = function(callback) {
+ var µb = µBlock;
+ var onReceived = function(lists) {
+ callback({
+ available: lists,
+ current: µb.remoteBlacklists,
+ cosmetic: µb.userSettings.parseAllABPHideFilters
+ });
+ };
+ µb.getAvailableLists(onReceived);
+};
+
+/******************************************************************************/
+
var onMessage = function(request, sender, callback) {
var µb = µBlock;
// Async
switch ( request.what ) {
+ case 'getLists':
+ return getLists(callback);
+
case 'readUserUbiquitousBlockRules':
return µb.assets.get(µb.userFiltersPath, callback);
diff --git a/js/messaging.js b/js/messaging.js
index d013e7ce3..bc5d4e7ce 100644
--- a/js/messaging.js
+++ b/js/messaging.js
@@ -138,6 +138,9 @@ function defaultHandler(request, sender, callback) {
// Async
switch ( request.what ) {
case 'getAssetContent':
+ if ( /^https?:\/\//.test(request.url) ) {
+ return µBlock.assets.getExternal(request.url, callback);
+ }
return µBlock.assets.get(request.url, callback);
case 'loadUbiquitousAllowRules':
diff --git a/js/pagestore.js b/js/pagestore.js
index 90822c4ef..2d160b1c5 100644
--- a/js/pagestore.js
+++ b/js/pagestore.js
@@ -154,7 +154,7 @@ PageStore.prototype.updateBadge = function() {
var iconStr = '';
if ( µb.userSettings.showIconBadge && netFilteringSwitch && this.perLoadBlockedRequestCount ) {
- iconStr = µb.formatCount(this.perLoadBlockedRequestCount);
+ iconStr = this.perLoadBlockedRequestCount.toLocaleString();
}
chrome.browserAction.setBadgeText({
tabId: this.tabId,
diff --git a/js/storage.js b/js/storage.js
index ae5a0ce8d..6cb995cd9 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -124,33 +124,100 @@
/******************************************************************************/
-µBlock.loadUbiquitousBlacklists = function() {
- var blacklistLoadCount;
- var obsoleteBlacklists = [];
+µBlock.getAvailableLists = function(callback) {
+ var availableLists = {};
- var removeObsoleteBlacklistsHandler = function(store) {
- if ( !store.remoteBlacklists ) {
- return;
- }
+ // selected lists
+ var onSelectedListsLoaded = function(store) {
+ var lists = store.remoteBlacklists;
+ var locations = Object.keys(lists);
var location;
- while ( location = obsoleteBlacklists.pop() ) {
- delete store.remoteBlacklists[location];
+
+ while ( location = locations.pop() ) {
+ if ( !availableLists[location] ) {
+ continue;
+ }
+ // https://github.com/gorhill/httpswitchboard/issues/218
+ // Transfer potentially existing list title into restored list data.
+ if ( lists[location].title !== availableLists[location].title ) {
+ lists[location].title = availableLists[location].title;
+ }
+ availableLists[location] = lists[location];
}
- chrome.storage.local.set(store);
+
+ callback(availableLists);
};
- var removeObsoleteBlacklists = function() {
- if ( obsoleteBlacklists.length === 0 ) {
- return;
+ // built-in lists
+ var onBuiltinListsLoaded = function(details) {
+ var location, locations;
+ try {
+ locations = JSON.parse(details.content);
+ } catch (e) {
+ locations = {};
}
+ for ( location in locations ) {
+ if ( locations.hasOwnProperty(location) === false ) {
+ continue;
+ }
+ availableLists['assets/thirdparties/' + location] = locations[location];
+ }
+
+ // Now get user's selection of lists
chrome.storage.local.get(
- { 'remoteBlacklists': µBlock.remoteBlacklists },
- removeObsoleteBlacklistsHandler
+ { 'remoteBlacklists': availableLists },
+ onSelectedListsLoaded
);
};
+ // permanent lists
+ var location;
+ var lists = this.permanentLists;
+ for ( location in lists ) {
+ if ( lists.hasOwnProperty(location) === false ) {
+ continue;
+ }
+ availableLists[location] = lists[location];
+ }
+
+ // custom lists
+ var c;
+ var locations = this.userSettings.externalLists.split('\n');
+ for ( var i = 0; i < locations.length; i++ ) {
+ location = locations[i].trim();
+ c = location.charAt(0);
+ if ( location === '' || c === '!' || c === '#' ) {
+ continue;
+ }
+ // Coarse validation
+ if ( /[^0-9A-Za-z!*'();:@&=+$,\/?%#\[\]_.~-]/.test(location) ) {
+ continue;
+ }
+ availableLists[location] = {
+ title: '',
+ group: 'custom',
+ external: true
+ };
+ }
+
+ // get built-in block lists.
+ this.assets.get('assets/ublock/filter-lists.json', onBuiltinListsLoaded);
+};
+
+/******************************************************************************/
+
+µBlock.loadUbiquitousBlacklists = function() {
+ var µb = this;
+ var blacklistLoadCount;
+
+ var loadBlacklistsEnd = function() {
+ µb.abpFilters.freeze();
+ µb.abpHideFilters.freeze();
+ µb.messaging.announce({ what: 'loadUbiquitousBlacklistCompleted' });
+ chrome.storage.local.set({ 'remoteBlacklists': µb.remoteBlacklists });
+ };
+
var mergeBlacklist = function(details) {
- var µb = µBlock;
µb.mergeUbiquitousBlacklist(details);
blacklistLoadCount -= 1;
if ( blacklistLoadCount === 0 ) {
@@ -158,97 +225,38 @@
}
};
- var loadBlacklistsEnd = function() {
- µBlock.abpFilters.freeze();
- µBlock.abpHideFilters.freeze();
- removeObsoleteBlacklists();
- µBlock.messaging.announce({ what: 'loadUbiquitousBlacklistCompleted' });
- };
-
- var loadBlacklistsStart = function(store) {
- var µb = µBlock;
+ var loadBlacklistsStart = function(lists) {
+ µb.remoteBlacklists = lists;
// rhill 2013-12-10: set all existing entries to `false`.
µb.abpFilters.reset();
µb.abpHideFilters.reset();
- var storedLists = store.remoteBlacklists;
- var storedListLocations = Object.keys(storedLists);
-
- blacklistLoadCount = storedListLocations.length;
+ var locations = Object.keys(lists);
+ blacklistLoadCount = locations.length;
if ( blacklistLoadCount === 0 ) {
loadBlacklistsEnd();
return;
}
- // Backward compatibility for when a list changes location
- var relocations = [
- {
- // https://github.com/gorhill/httpswitchboard/issues/361
- 'bad': 'assets/thirdparties/adblock-czechoslovaklist.googlecode.com/svn/filters.txt',
- 'good': 'assets/thirdparties/raw.githubusercontent.com/tomasko126/easylistczechandslovak/master/filters.txt'
- }
- ];
- var relocation;
- while ( relocation = relocations.pop() ) {
- if ( µb.remoteBlacklists[relocation.good] && storedLists[relocation.bad] ) {
- storedLists[relocation.good].off = storedLists[relocation.bad].off;
- }
- }
-
// Load each preset blacklist which is not disabled.
var location;
- while ( location = storedListLocations.pop() ) {
- // If loaded list location is not part of default list locations,
- // remove its entry from local storage.
- if ( !µb.remoteBlacklists[location] ) {
- obsoleteBlacklists.push(location);
- blacklistLoadCount -= 1;
- continue;
- }
- // https://github.com/gorhill/httpswitchboard/issues/218
- // Transfer potentially existing list title into restored list data.
- if ( storedLists[location].title !== µb.remoteBlacklists[location].title ) {
- storedLists[location].title = µb.remoteBlacklists[location].title;
- }
- // Store details of this preset blacklist
- µb.remoteBlacklists[location] = storedLists[location];
+ while ( location = locations.pop() ) {
// rhill 2013-12-09:
// Ignore list if disabled
// https://github.com/gorhill/httpswitchboard/issues/78
- if ( storedLists[location].off ) {
+ if ( lists[location].off ) {
blacklistLoadCount -= 1;
continue;
}
- µb.assets.get(location, mergeBlacklist);
- }
- };
-
- var onListOfBlockListsLoaded = function(details) {
- var µb = µBlock;
- // Initialize built-in list of 3rd-party block lists.
- var lists = JSON.parse(details.content);
- for ( var location in lists ) {
- if ( lists.hasOwnProperty(location) === false ) {
- continue;
+ if ( /^https?:\/\/.+$/.test(location) ) {
+ µb.assets.getExternal(location, mergeBlacklist);
+ } else {
+ µb.assets.get(location, mergeBlacklist);
}
- µb.remoteBlacklists['assets/thirdparties/' + location] = lists[location];
}
- // Now get user's selection of list of block lists.
- chrome.storage.local.get(
- { 'remoteBlacklists': µb.remoteBlacklists },
- loadBlacklistsStart
- );
};
- // Reset list of 3rd-party block lists.
- for ( var location in this.remoteBlacklists ) {
- if ( location.indexOf('assets/thirdparties/') === 0 ) {
- delete this.remoteBlacklists[location];
- }
- }
-
- // Get new list of 3rd-party block lists.
- this.assets.get('assets/ublock/filter-lists.json', onListOfBlockListsLoaded);
+ this.getAvailableLists(loadBlacklistsStart);
};
/******************************************************************************/