This commit is contained in:
gorhill 2015-03-10 23:46:18 -04:00
parent afa08caa6a
commit 39ad1585e9
8 changed files with 346 additions and 163 deletions

View file

@ -12,7 +12,7 @@
<button id="buttonApply" class="custom reloadAll disabled" data-i18n="3pApplyChanges"></button>
<ul id="options">
<li><input type="checkbox" id="autoUpdate"><label data-i18n="3pAutoUpdatePrompt1" for="autoUpdate"></label>
<li><input type="checkbox" id="autoUpdate"><label data-i18n="3pAutoUpdatePrompt1" for="autoUpdate"></label>&ensp;
<button class="custom reloadAll disabled" id="buttonUpdate" data-i18n="3pUpdateNow"></button>
<button id="buttonPurgeAll" class="custom disabled" data-i18n="3pPurgeAll"></button>
<li><input type="checkbox" id="parseCosmeticFilters"><label data-i18n="3pParseAllABPHideFiltersPrompt1" for="parseCosmeticFilters"></label>
@ -28,7 +28,11 @@
<p style="margin: 0.25em 0 0 0"><button id="externalListsApply" disabled="true" data-i18n="3pExternalListsApply"></button></p>
</div>
<div id="busyOverlay"></div>
<div id="busyOverlay">
<div></div>
<!-- progress bar widget -->
<div><div></div><div></div></div>
</div>
<div id="templates" style="display: none;">
<ul>

View file

@ -128,17 +128,63 @@ body[dir=rtl] #externalListsDiv {
word-wrap: normal;
}
body #busyOverlay {
position: fixed;
top: 0;
right: 0;
background-color: transparent;
bottom: 0;
left: 0;
background-color: white;
opacity: 0.5;
cursor: wait;
display: none;
left: 0;
position: fixed;
right: 0;
top: 0;
z-index: 1000;
}
body.busy #busyOverlay {
display: block;
}
#busyOverlay > div:nth-of-type(1) {
background-color: white;
bottom: 0;
left: 0;
opacity: 0.75;
position: absolute;
right: 0;
top: 0;
}
#busyOverlay > div:nth-of-type(2) {
background-color: #eee;
border: 1px solid transparent;
border-color: #80b3ff #80b3ff hsl(216, 100%, 75%);
border-radius: 3px;
box-sizing: border-box;
height: 3em;
left: 10%;
position: absolute;
bottom: 75%;
width: 80%;
}
#busyOverlay > div:nth-of-type(2) > div:nth-of-type(1) {
background-color: hsl(216, 100%, 75%);
background-image: linear-gradient(#a8cbff, #80b3ff);
background-repeat: repeat-x;
border: 0;
box-sizing: border-box;
color: #222;
height: 100%;
left: 0;
padding: 0;
position: absolute;
width: 25%;
}
#busyOverlay > div:nth-of-type(2) > div:nth-of-type(2) {
background-color: transparent;
border: 0;
box-sizing: border-box;
height: 100%;
left: 0;
line-height: 3em;
overflow: hidden;
position: absolute;
text-align: center;
top: 0;
width: 100%;
}

View file

@ -42,7 +42,14 @@ var hasCachedContent = false;
var onMessage = function(msg) {
switch ( msg.what ) {
case 'allFilterListsReloaded':
renderBlacklists();
renderFilterLists();
break;
case 'forceUpdateAssetsProgress':
renderBusyOverlay(true, msg.progress);
if ( msg.done ) {
messager.send({ what: 'reloadAllFilters' });
}
break;
default:
@ -62,13 +69,12 @@ var renderNumber = function(value) {
// TODO: get rid of background page dependencies
var renderBlacklists = function() {
uDom('body').toggleClass('busy', true);
var renderFilterLists = function() {
var listGroupTemplate = uDom('#templates .groupEntry');
var listEntryTemplate = uDom('#templates .listEntry');
var listStatsTemplate = vAPI.i18n('3pListsOfBlockedHostsPerListStats');
var renderElapsedTimeToString = vAPI.i18n.renderElapsedTimeToString;
var lastUpdateString = vAPI.i18n('3pLastUpdate');
// Assemble a pretty blacklist name if possible
var listNameFromListKey = function(listKey) {
@ -130,7 +136,7 @@ var renderBlacklists = function() {
if ( asset.cached ) {
elem = li.descendants('span.status.purge');
elem.css('display', '');
elem.attr('title', renderElapsedTimeToString(asset.lastModified));
elem.attr('title', lastUpdateString.replace('{{ago}}', renderElapsedTimeToString(asset.lastModified)));
hasCachedContent = true;
}
return li;
@ -212,7 +218,8 @@ var renderBlacklists = function() {
uDom('#autoUpdate').prop('checked', listDetails.autoUpdate === true);
uDom('#parseCosmeticFilters').prop('checked', listDetails.cosmetic === true);
updateWidgets();
renderWidgets();
renderBusyOverlay(details.manualUpdate, details.manualUpdateProgress);
};
messager.send({ what: 'getLists' }, onListsReceived);
@ -220,18 +227,52 @@ var renderBlacklists = function() {
/******************************************************************************/
// Progress must be normalized to [0, 1], or can be undefined.
var renderBusyOverlay = function(state, progress) {
progress = progress || {};
var showProgress = typeof progress.value === 'number';
if ( showProgress ) {
uDom('#busyOverlay > div:nth-of-type(2) > div:first-child').css(
'width',
(progress.value * 100).toFixed(1) + '%'
);
var text = progress.text || '';
if ( text !== '' ) {
uDom('#busyOverlay > div:nth-of-type(2) > div:last-child').text(text);
}
}
uDom('#busyOverlay > div:nth-of-type(2)').css('display', showProgress ? '' : 'none');
uDom('body').toggleClass('busy', !!state);
};
/******************************************************************************/
// This is to give a visual hint that the selection of blacklists has changed.
var renderWidgets = function() {
uDom('#buttonApply').toggleClass('disabled', !listsSelectionChanged());
uDom('#buttonUpdate').toggleClass('disabled', !listsContentChanged());
uDom('#buttonPurgeAll').toggleClass('disabled', !hasCachedContent);
};
/******************************************************************************/
// Return whether selection of lists changed.
var listsSelectionChanged = function() {
if ( listDetails.cosmetic !== cosmeticSwitch ) {
return true;
}
if ( cacheWasPurged ) {
return true;
}
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 ) {
@ -243,6 +284,7 @@ var listsSelectionChanged = function() {
return true;
}
}
// This check removed entries
for ( location in currentLists ) {
if ( currentLists.hasOwnProperty(location) === false ) {
@ -254,6 +296,7 @@ var listsSelectionChanged = function() {
return true;
}
}
return false;
};
@ -267,17 +310,6 @@ var listsContentChanged = function() {
/******************************************************************************/
// This is to give a visual hint that the selection of blacklists has changed.
var updateWidgets = function() {
uDom('#buttonApply').toggleClass('disabled', !listsSelectionChanged());
uDom('#buttonUpdate').toggleClass('disabled', !listsContentChanged());
uDom('#buttonPurgeAll').toggleClass('disabled', !hasCachedContent);
uDom('body').toggleClass('busy', false);
};
/******************************************************************************/
var onListCheckboxChanged = function() {
var href = uDom(this).parent().descendants('a').first().attr('href');
if ( typeof href !== 'string' ) {
@ -287,7 +319,7 @@ var onListCheckboxChanged = function() {
return;
}
listDetails.available[href].off = !this.checked;
updateWidgets();
renderWidgets();
};
/******************************************************************************/
@ -317,24 +349,21 @@ var onPurgeClicked = function() {
button.remove();
if ( li.descendants('input').first().prop('checked') ) {
cacheWasPurged = true;
updateWidgets();
renderWidgets();
}
};
/******************************************************************************/
var reloadAll = function(update) {
// Loading may take a while when resources are fetched from remote
// servers. We do not want the user to force reload while we are reloading.
uDom('body').toggleClass('busy', true);
// Reload blacklists
var selectFilterLists = function(callback) {
// Cosmetic filtering switch
messager.send({
what: 'userSettings',
name: 'parseAllABPHideFilters',
value: listDetails.cosmetic
});
// Reload blacklists
// Filter lists
var switches = [];
var lis = uDom('#lists .listEntry');
var i = lis.length;
@ -349,18 +378,30 @@ var reloadAll = function(update) {
off: lis.subset(i, 1).descendants('input').prop('checked') === false
});
}
messager.send({
what: 'reloadAllFilters',
switches: switches,
update: update
});
cacheWasPurged = false;
what: 'selectFilterLists',
switches: switches
}, callback);
};
/******************************************************************************/
var buttonApplyHandler = function() {
reloadAll(false);
renderBusyOverlay(true);
var onReloadDone = function() {
messager.send({ what: 'reloadAllFilters' });
};
var onSelectionDone = function() {
messager.send({ what: 'reloadAllFilters' }, onReloadDone);
};
selectFilterLists(onSelectionDone);
cacheWasPurged = false;
uDom('#buttonApply').toggleClass('enabled', false);
};
@ -368,16 +409,28 @@ var buttonApplyHandler = function() {
var buttonUpdateHandler = function() {
if ( needUpdate ) {
reloadAll(true);
renderBusyOverlay(true);
var onSelectionDone = function() {
messager.send({ what: 'forceUpdateAssets' });
};
selectFilterLists(onSelectionDone);
cacheWasPurged = false;
}
uDom('#buttonPurgeAll').toggleClass('enabled', false);
};
/******************************************************************************/
var buttonPurgeAllHandler = function() {
var onCompleted = function() {
renderBlacklists();
cacheWasPurged = true;
renderFilterLists();
};
messager.send({ what: 'purgeAllCaches' }, onCompleted);
};
@ -395,7 +448,7 @@ var autoUpdateCheckboxChanged = function() {
var cosmeticSwitchChanged = function() {
listDetails.cosmetic = this.checked;
updateWidgets();
renderWidgets();
};
/******************************************************************************/
@ -426,7 +479,7 @@ var externalListsApplyHandler = function() {
name: 'externalLists',
value: externalLists
});
renderBlacklists();
renderFilterLists();
uDom('#externalListsApply').prop('disabled', true);
};
@ -444,7 +497,7 @@ uDom.onLoad(function() {
uDom('#externalLists').on('input', externalListsChangeHandler);
uDom('#externalListsApply').on('click', externalListsApplyHandler);
renderBlacklists();
renderFilterLists();
renderExternalLists();
});

View file

@ -53,7 +53,6 @@ var oneDay = 24 * oneHour;
/******************************************************************************/
var projectRepositoryRoot = µBlock.projectServerRoot;
var thirdpartiesRepositoryRoot = 'https://raw.githubusercontent.com/gorhill/uAssets/master/src';
var nullFunc = function() {};
var reIsExternalPath = /^[a-z]+:\/\//;
var reIsUserPath = /^assets\/user\//;
@ -1117,7 +1116,10 @@ return exports;
var µb = µBlock;
var updateDaemonTimerPeriod = 11 * 60 * 1000; // 11 minutes
var updateDaemonTimer = null;
var autoUpdateDaemonTimerPeriod = 11 * 60 * 1000; // 11 minutes
var manualUpdateDaemonTimerPeriod = 5 * 1000; // 5 seconds
var updateCycleFirstPeriod = 7 * 60 * 1000; // 7 minutes
var updateCycleNextPeriod = 11 * 60 * 60 * 1000; // 11 hours
var updateCycleTime = 0;
@ -1132,28 +1134,44 @@ var onStartListener = null;
var onCompletedListener = null;
var onAssetUpdatedListener = null;
var exports = {};
var exports = {
manualUpdate: false,
manualUpdateProgress: {
value: 0,
text: null
}
};
/******************************************************************************/
var onAssetUpdated = function(details) {
// Resource fetched, we can safely restart the daemon.
scheduleUpdateDaemon();
var path = details.path;
if ( details.error ) {
//console.debug('µBlock.assetUpdater/onAssetUpdated: "%s" failed', path);
return;
}
//console.debug('µBlock.assetUpdater/onAssetUpdated: "%s"', path);
updated[path] = true;
updatedCount += 1;
if ( typeof onAssetUpdatedListener === 'function' ) {
onAssetUpdatedListener(details);
}
manualUpdateNotify(false, updatedCount / (updatedCount + toUpdateCount + 1));
};
/******************************************************************************/
var updateOne = function() {
var metaEntry;
var updatingCount = 0;
var updatingText = null;
for ( var path in toUpdate ) {
if ( toUpdate.hasOwnProperty(path) === false ) {
continue;
@ -1170,10 +1188,25 @@ var updateOne = function() {
if ( !metaEntry.cacheObsolete && !metaEntry.repoObsolete ) {
continue;
}
// Will restart the update daemon once the resource is received: the
// fetching of a resource may take some time, possibly beyond the
// next scheduled daemon cycle, so this ensure the daemon won't do
// anything else before the resource is fetched (or times out).
suspendUpdateDaemon();
//console.debug('µBlock.assetUpdater/updateOne: assets.get("%s")', path);
µb.assets.get(path, onAssetUpdated);
updatingCount = 1;
updatingText = metaEntry.homeURL || path;
break;
}
manualUpdateNotify(
false,
(updatedCount + updatingCount/2) / (updatedCount + toUpdateCount + updatingCount + 1),
updatingText
);
};
/******************************************************************************/
@ -1186,9 +1219,10 @@ var onMetadataReady = function(response) {
/******************************************************************************/
var updateDaemon = function() {
setTimeout(updateDaemon, updateDaemonTimerPeriod);
updateDaemonTimer = null;
scheduleUpdateDaemon();
µb.assets.autoUpdate = µb.userSettings.autoUpdate;
µb.assets.autoUpdate = µb.userSettings.autoUpdate || exports.manualUpdate;
if ( µb.assets.autoUpdate !== true ) {
return;
@ -1217,6 +1251,9 @@ var updateDaemon = function() {
}
// Nothing left to update
// In case of manual update, fire progress notifications
manualUpdateNotify(true, 1, '');
// If anything was updated, notify listener
if ( updatedCount !== 0 ) {
if ( typeof onCompletedListener === 'function' ) {
@ -1236,7 +1273,26 @@ var updateDaemon = function() {
}
};
setTimeout(updateDaemon, updateDaemonTimerPeriod);
/******************************************************************************/
var scheduleUpdateDaemon = function() {
if ( updateDaemonTimer !== null ) {
clearTimeout(updateDaemonTimer);
}
updateDaemonTimer = setTimeout(
updateDaemon,
exports.manualUpdate ? manualUpdateDaemonTimerPeriod : autoUpdateDaemonTimerPeriod
);
};
var suspendUpdateDaemon = function() {
if ( updateDaemonTimer !== null ) {
clearTimeout(updateDaemonTimer);
updateDaemonTimer = null;
}
};
scheduleUpdateDaemon();
/******************************************************************************/
@ -1251,6 +1307,67 @@ var reset = function() {
/******************************************************************************/
// Manual update: just a matter of forcing the update daemon to work on a
// tighter schedule.
exports.force = function() {
if ( exports.manualUpdate ) {
return;
}
if ( updateDaemonTimer !== null ) {
clearTimeout(updateDaemonTimer);
}
reset();
if ( typeof onStartListener === 'function' ) {
onStartListener();
}
// This must be done here
exports.manualUpdate = true;
if ( toUpdateCount === 0 ) {
updateCycleTime = Date.now() + updateCycleNextPeriod;
scheduleUpdateDaemon();
manualUpdateNotify(true, 1);
return;
}
scheduleUpdateDaemon();
manualUpdateNotify(false, 0);
};
/******************************************************************************/
var manualUpdateNotify = function(done, value, text) {
if ( exports.manualUpdate === false ) {
return;
}
exports.manualUpdate = !done;
exports.manualUpdateProgress.value = value || 0;
if ( typeof text === 'string' ) {
exports.manualUpdateProgress.text = text;
}
vAPI.messaging.broadcast({
what: 'forceUpdateAssetsProgress',
done: !exports.manualUpdate,
progress: exports.manualUpdateProgress,
updatedCount: updatedCount
});
// When manually updating, whatever launched the manual update is
// responsible to launch a reload of the filter lists.
if ( exports.manualUpdate !== true ) {
reset();
}
};
/******************************************************************************/
exports.onStart = {
addEventListener: function(callback) {
onStartListener = callback || null;

View file

@ -42,6 +42,10 @@ var onMessage = function(request, sender, callback) {
µb.assets.get(request.url, callback);
return;
case 'reloadAllFilters':
µb.reloadAllFilters(callback);
return;
default:
break;
}
@ -55,6 +59,10 @@ var onMessage = function(request, sender, callback) {
µb.contextMenuClientY = request.clientY;
break;
case 'forceUpdateAssets':
µb.assetUpdater.force();
break;
case 'getAppData':
response = {name: vAPI.app.name, version: vAPI.app.version};
break;
@ -67,16 +75,16 @@ var onMessage = function(request, sender, callback) {
vAPI.tabs.open(request.details);
break;
case 'reloadAllFilters':
µb.reloadFilterLists(request.switches, request.update);
break;
case 'reloadTab':
if ( vAPI.isNoTabId(request.tabId) === false ) {
vAPI.tabs.reload(request.tabId);
}
break;
case 'selectFilterLists':
µb.selectFilterLists(request.switches);
break;
case 'userSettings':
response = µb.changeUserSettings(request.name, request.value);
break;
@ -634,17 +642,20 @@ var prepEntries = function(entries) {
var getLists = function(callback) {
var r = {
available: null,
current: µb.remoteBlacklists,
cosmetic: µb.userSettings.parseAllABPHideFilters,
netFilterCount: µb.staticNetFilteringEngine.getFilterCount(),
cosmeticFilterCount: µb.cosmeticFilteringEngine.getFilterCount(),
autoUpdate: µb.userSettings.autoUpdate,
userFiltersPath: µb.userFiltersPath,
cache: null
available: null,
cache: null,
cosmetic: µb.userSettings.parseAllABPHideFilters,
cosmeticFilterCount: µb.cosmeticFilteringEngine.getFilterCount(),
current: µb.remoteBlacklists,
manualUpdate: false,
netFilterCount: µb.staticNetFilteringEngine.getFilterCount(),
userFiltersPath: µb.userFiltersPath
};
var onMetadataReady = function(entries) {
r.cache = entries;
r.manualUpdate = µb.assetUpdater.manualUpdate;
r.manualUpdateProgress = µb.assetUpdater.manualUpdateProgress;
prepEntries(r.cache);
callback(r);
};

View file

@ -27,6 +27,8 @@
(function() {
'use strict';
//quickProfiler.start('start.js');
/******************************************************************************/
@ -126,6 +128,7 @@ var onUserSettingsReady = function(fetched) {
// time.
µb.assets.allowRemoteFetch = false;
µb.assets.autoUpdate = userSettings.autoUpdate;
µb.assets.autoUpdateDelay = µb.updateAssetsEvery;
// https://github.com/gorhill/uBlock/issues/540
// Disabling local mirroring for the time being

View file

@ -182,8 +182,15 @@
if ( storedEntry.entryUsedCount !== undefined ) {
availableEntry.entryUsedCount = storedEntry.entryUsedCount;
}
// This may happen if the list name was pulled from the list content
if ( availableEntry.title === '' && storedEntry.title !== '' ) {
// This may happen if the list name was pulled from the list
// content.
// https://github.com/gorhill/uBlock/issues/982
// There is no guarantee the title was successfully extracted from
// the list content.
if ( availableEntry.title === '' &&
typeof storedEntry.title === 'string' &&
storedEntry.title !== ''
) {
availableEntry.title = storedEntry.title;
}
}
@ -514,78 +521,47 @@
// `switches` contains the filter lists for which the switch must be revisited.
µBlock.reloadFilterLists = function(switches, update) {
var µb = this;
µBlock.selectFilterLists = function(switches) {
switches = switches || {};
var onFilterListsReady = function() {
µb.loadUpdatableAssets({ update: update, psl: update });
};
var onPurgeDone = function() {
// Toggle switches, if any
if ( switches === undefined ) {
onFilterListsReady();
return;
}
// Only the lists referenced by the switches are touched.
var filterLists = µb.remoteBlacklists;
var entry, state, location;
var i = switches.length;
while ( i-- ) {
entry = switches[i];
state = entry.off === true;
location = entry.location;
if ( filterLists.hasOwnProperty(location) === false ) {
if ( state !== true ) {
filterLists[location] = { off: state };
}
continue;
// Only the lists referenced by the switches are touched.
var filterLists = this.remoteBlacklists;
var entry, state, location;
var i = switches.length;
while ( i-- ) {
entry = switches[i];
state = entry.off === true;
location = entry.location;
if ( filterLists.hasOwnProperty(location) === false ) {
if ( state !== true ) {
filterLists[location] = { off: state };
}
if ( filterLists[location].off === state ) {
continue;
}
filterLists[location].off = state;
continue;
}
vAPI.storage.set({ 'remoteBlacklists': filterLists }, onFilterListsReady);
};
// If we must update, we need to purge the compiled versions of
// obsolete assets.
if ( update !== true ) {
onPurgeDone();
return;
if ( filterLists[location].off === state ) {
continue;
}
filterLists[location].off = state;
}
var onMetadataReady = function(metadata) {
var filterLists = µb.remoteBlacklists;
var entry;
// Purge obsolete filter lists
for ( var path in filterLists ) {
if ( filterLists.hasOwnProperty(path) === false ) {
continue;
}
if ( metadata.hasOwnProperty(path) === false ) {
continue;
}
entry = metadata[path];
if ( entry.repoObsolete !== true && entry.cacheObsolete !== true ) {
continue;
}
µb.purgeCompiledFilterList(path);
}
// Purge obsolete PSL
if ( metadata.hasOwnProperty(µb.pslPath) ) {
if ( metadata[µb.pslPath].repoObsolete === true ) {
µb.assets.purge('cache://compiled-publicsuffixlist');
}
}
onPurgeDone();
vAPI.storage.set({ 'remoteBlacklists': filterLists });
};
/******************************************************************************/
// Plain reload of all filters.
µBlock.reloadAllFilters = function() {
var µb = this;
// We are just reloading the filter lists: we do not want assets to update.
this.assets.autoUpdate = false;
var onFiltersReady = function() {
µb.assets.autoUpdate = µb.userSettings.autoUpdate;
};
this.assets.metadata(onMetadataReady);
this.loadFilterLists(onFiltersReady);
};
/******************************************************************************/
@ -623,36 +599,6 @@
/******************************************************************************/
// Load updatable assets
µBlock.loadUpdatableAssets = function(details) {
var µb = this;
details = details || {};
var update = details.update !== false;
this.assets.autoUpdate = update || this.userSettings.autoUpdate;
this.assets.autoUpdateDelay = this.updateAssetsEvery;
var onFiltersReady = function() {
if ( update ) {
µb.assetUpdater.restart();
}
};
var onPSLReady = function() {
µb.loadFilterLists(onFiltersReady);
};
if ( details.psl !== false ) {
this.loadPublicSuffixList(onPSLReady);
} else {
this.loadFilterLists(onFiltersReady);
}
};
/******************************************************************************/
µBlock.toSelfie = function() {
var selfie = {
magic: this.systemSettings.selfieMagic,

View file

@ -84,12 +84,15 @@ var onAbpLinkClicked = function(ev) {
ev.stopPropagation();
ev.preventDefault();
var onListsSelectionDone = function() {
messager.send({ what: 'reloadAllFilters' });
};
var onExternalListsSaved = function() {
messager.send({
what: 'reloadAllFilters',
switches: [ { location: location, off: false } ],
update: false
});
what: 'selectFilterLists',
switches: [ { location: location, off: false } ]
}, onListsSelectionDone);
};
var onSubscriberDataReady = function(details) {