diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json
index f2e7977..fc62a15 100644
--- a/src/_locales/en/messages.json
+++ b/src/_locales/en/messages.json
@@ -449,6 +449,10 @@
"message": "{{used}} used out of {{total}}",
"description": ""
},
+ "hostsFilesLastUpdate" : {
+ "message":"Last update: {{ago}}",
+ "description":"English: Last update: {{ago}}, where 'ago' will be replaced with something like '2 days ago'"
+ },
"hostsFilesApplyChanges" : {
"message": "Apply changes",
"description": ""
@@ -582,6 +586,30 @@
"description": ""
},
+ "elapsedOneMinuteAgo":{
+ "message":"a minute ago",
+ "description":"English: a minute ago"
+ },
+ "elapsedManyMinutesAgo":{
+ "message":"{{value}} minutes ago",
+ "description":"English: {{value}} minutes ago"
+ },
+ "elapsedOneHourAgo":{
+ "message":"an hour ago",
+ "description":"English: an hour ago"
+ },
+ "elapsedManyHoursAgo":{
+ "message":"{{value}} hours ago",
+ "description":"English: {{value}} hours ago"
+ },
+ "elapsedOneDayAgo":{
+ "message":"a day ago",
+ "description":"English: a day ago"
+ },
+ "elapsedManyDaysAgo":{
+ "message":"{{value}} days ago",
+ "description":"English: {{value}} days ago"
+ },
"errorCantConnectTo":{
"message":"Network error: Unable to connect to {{url}}",
diff --git a/src/asset-viewer.html b/src/asset-viewer.html
index bca8c9e..f675290 100644
--- a/src/asset-viewer.html
+++ b/src/asset-viewer.html
@@ -12,6 +12,7 @@
+
diff --git a/src/background.html b/src/background.html
index 026f61e..f873fc8 100644
--- a/src/background.html
+++ b/src/background.html
@@ -19,7 +19,6 @@
-
diff --git a/src/css/hosts-files.css b/src/css/hosts-files.css
index 05671ac..da5b114 100644
--- a/src/css/hosts-files.css
+++ b/src/css/hosts-files.css
@@ -10,19 +10,25 @@ ul#options li {
}
ul#lists {
margin: 0.5em 0 0 0;
- padding-__MSG_@@bidi_end_edge__: 0em;
- padding-__MSG_@@bidi_start_edge__: 1em;
+ padding: 0;
}
-li.listDetails {
+li.listEntry {
font-size: 14px;
margin: 0 auto 0 auto;
- margin-__MSG_@@bidi_start_edge__: 1em;
- margin-__MSG_@@bidi_end_edge__: 0em;
+ padding: 0.2em 0;
+ }
+body[dir="ltr"] li.listEntry {
+ margin-left: 1em;
+ margin-right: 0em;
+ }
+body[dir="rtl"] li.listEntry {
+ margin-left: 0em;
+ margin-right: 1em;
}
-li.listDetails > * {
+li.listEntry > * {
unicode-bidi: embed;
}
-li.listDetails > a:nth-of-type(2) {
+li.listEntry > a:nth-of-type(2) {
font-size: 13px;
opacity: 0.5;
}
@@ -58,25 +64,36 @@ button.custom.reloadAll:not(.disabled) {
background-image: linear-gradient(#ffdca8, #ffcc7f);
}
#buttonApply {
- position: fixed;
display: initial;
+ padding: 1em 1em;
+ position: fixed;
top: 1em;
- __MSG_@@bidi_end_edge__: 1em;
+ }
+body[dir="ltr"] #buttonApply {
+ right: 1em;
+ }
+body[dir="rtl"] #buttonApply {
+ left: 1em;
}
#buttonApply.disabled {
display: none;
}
span.status {
- margin: 0;
border: 1px solid transparent;
- padding: 1px 2px;
+ color: #444;
display: inline-block;
- font-size: 11px;
- opacity: 0.7;
+ 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%);
+ }
span.purge {
border-color: #ddd;
- color: #444;
background-color: #eee;
cursor: pointer;
}
@@ -85,31 +102,82 @@ span.purge:hover {
}
span.obsolete {
border-color: hsl(36, 100%, 73%);
- color: #222;
background-color: hsl(36, 100%, 75%);
}
#externalListsDiv {
margin: 2em auto 0 auto;
- margin-__MSG_@@bidi_start_edge__: 2em;
+ }
+body[dir="ltr"] #externalListsDiv {
+ margin-left: 1em;
+ }
+body[dir="rtl"] #externalListsDiv {
+ margin-right: 1em;
}
#externalHostsFiles {
+ box-sizing: border-box;
font-size: smaller;
- width: 48em;
+ width: 100%;
height: 12em;
white-space: nowrap;
}
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%;
+ }
diff --git a/src/hosts-files.html b/src/hosts-files.html
index 55f00c5..c37bae6 100644
--- a/src/hosts-files.html
+++ b/src/hosts-files.html
@@ -27,7 +27,29 @@
-
+
+
+
diff --git a/src/js/assets.js b/src/js/assets.js
index 16b4127..40eb45d 100644
--- a/src/js/assets.js
+++ b/src/js/assets.js
@@ -1121,7 +1121,7 @@ exports.purgeAll = function(callback) {
/******************************************************************************/
exports.onAssetCacheRemoved = {
- addEventListener: function(callback) {
+ addListener: function(callback) {
cachedAssetsManager.onRemovedListener = callback || null;
}
};
@@ -1436,7 +1436,7 @@ exports.force = function() {
/******************************************************************************/
exports.onStart = {
- addEventListener: function(callback) {
+ addListener: function(callback) {
onStartListener = callback || null;
if ( typeof onStartListener === 'function' ) {
updateCycleTime = Date.now() + updateCycleFirstPeriod;
@@ -1447,7 +1447,7 @@ exports.onStart = {
/******************************************************************************/
exports.onAssetUpdated = {
- addEventListener: function(callback) {
+ addListener: function(callback) {
onAssetUpdatedListener = callback || null;
}
};
@@ -1455,7 +1455,7 @@ exports.onAssetUpdated = {
/******************************************************************************/
exports.onCompleted = {
- addEventListener: function(callback) {
+ addListener: function(callback) {
onCompletedListener = callback || null;
}
};
diff --git a/src/js/background.js b/src/js/background.js
index 466e8ba..75c3971 100644
--- a/src/js/background.js
+++ b/src/js/background.js
@@ -86,10 +86,6 @@ return {
// permanent hosts files
permanentHostsFiles: {
- // µMatrix
- 'assets/umatrix/blacklist.txt': {
- title: 'µMatrix hosts file'
- }
},
// list of live hosts files
diff --git a/src/js/hosts-files.js b/src/js/hosts-files.js
index f5ffeb4..01e8930 100644
--- a/src/js/hosts-files.js
+++ b/src/js/hosts-files.js
@@ -1,7 +1,7 @@
/*******************************************************************************
µMatrix - a Chromium browser extension to black/white list requests.
- Copyright (C) 2014 Raymond Hill
+ Copyright (C) 2014-2015 Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -29,14 +29,29 @@
/******************************************************************************/
+var listDetails = {};
+var externalHostsFiles = '';
+var cacheWasPurged = false;
+var needUpdate = false;
+var hasCachedContent = false;
+
+/******************************************************************************/
+
var onMessage = function(msg) {
switch ( msg.what ) {
- case 'loadHostsFilesCompleted':
- renderBlacklists();
- break;
+ case 'loadHostsFilesCompleted':
+ renderHostsFiles();
+ break;
+
+ case 'forceUpdateAssetsProgress':
+ renderBusyOverlay(true, msg.progress);
+ if ( msg.done ) {
+ messager.send({ what: 'reloadHostsFiles' });
+ }
+ break;
- default:
- break;
+ default:
+ break;
}
};
@@ -44,21 +59,19 @@ var messager = vAPI.messaging.channel('hosts-files.js', onMessage);
/******************************************************************************/
-var listDetails = {};
-var externalHostsFiles = '';
-var cacheWasPurged = false;
-var needUpdate = false;
-var hasCachedContent = false;
-
-var re3rdPartyExternalAsset = /^https?:\/\/[a-z0-9]+/;
-var re3rdPartyRepoAsset = /^assets\/thirdparties\/([^\/]+)/;
+var renderNumber = function(value) {
+ return value.toLocaleString();
+};
/******************************************************************************/
// TODO: get rid of background page dependencies
-var renderBlacklists = function() {
- uDom('body').toggleClass('busy', true);
+var renderHostsFiles = function() {
+ var listEntryTemplate = uDom('#templates .listEntry');
+ var listStatsTemplate = vAPI.i18n('hostsFilesPerFileStats');
+ var lastUpdateString = vAPI.i18n('hostsFilesLastUpdate');
+ var renderElapsedTimeToString = vAPI.i18n.renderElapsedTimeToString;
// Assemble a pretty blacklist name if possible
var listNameFromListKey = function(listKey) {
@@ -70,89 +83,67 @@ var renderBlacklists = function() {
return listTitle;
};
- // Assemble a pretty blacklist name if possible
- var htmlFromHomeURL = function(blacklistHref) {
- if ( blacklistHref.indexOf('assets/thirdparties/') !== 0 ) {
- return '';
- }
- var matches = re3rdPartyRepoAsset.exec(blacklistHref);
- if ( matches === null || matches.length !== 2 ) {
- return '';
+ var liFromListEntry = function(listKey) {
+ var elem, text;
+ var entry = listDetails.available[listKey];
+ var li = listEntryTemplate.clone();
+
+ if ( entry.off !== true ) {
+ li.descendants('input').attr('checked', '');
}
- var hostname = matches[1];
- var domain = hostname;
- if ( domain === '' ) {
- return '';
+
+ elem = li.descendants('a:nth-of-type(1)');
+ elem.attr('href', encodeURI(listKey));
+ elem.text(listNameFromListKey(listKey) + '\u200E');
+
+ elem = li.descendants('a:nth-of-type(2)');
+ if ( entry.homeDomain ) {
+ elem.attr('href', 'http://' + encodeURI(entry.homeHostname));
+ elem.text('(' + entry.homeDomain + ')');
+ elem.css('display', '');
}
- var html = [
- ' (',
- domain,
- ')'
- ];
- return html.join('');
- };
- var purgeButtontext = vAPI.i18n('hostsFilesExternalListPurge');
- var updateButtontext = vAPI.i18n('hostsFilesExternalListNew');
- var obsoleteButtontext = vAPI.i18n('hostsFilesExternalListObsolete');
- var liTemplate = [
- '',
- '',
- ' ',
- '',
- '{{name}}',
- '\u200E',
- '{{homeURL}}',
- ': ',
- '',
- vAPI.i18n('hostsFilesPerFileStats'),
- ''
- ].join('');
-
- var htmlFromLeaf = function(listKey) {
- var html = [];
- var hostsEntry = listDetails.available[listKey];
- var li = liTemplate
- .replace('{{checked}}', hostsEntry.off ? '' : 'checked')
- .replace('{{URL}}', encodeURI(listKey))
- .replace('{{name}}', listNameFromListKey(listKey))
- .replace('{{homeURL}}', htmlFromHomeURL(listKey))
- .replace('{{used}}', !hostsEntry.off && !isNaN(+hostsEntry.entryUsedCount) ? hostsEntry.entryUsedCount.toLocaleString() : '0')
- .replace('{{total}}', !isNaN(+hostsEntry.entryCount) ? hostsEntry.entryCount.toLocaleString() : '?');
- html.push(li);
- // https://github.com/gorhill/uBlock/issues/104
- var asset = listDetails.cache[listKey];
- if ( asset === undefined ) {
- return html.join('\n');
+ elem = li.descendants('span:nth-of-type(1)');
+ text = listStatsTemplate
+ .replace('{{used}}', renderNumber(!entry.off && !isNaN(+entry.entryUsedCount) ? entry.entryUsedCount : 0))
+ .replace('{{total}}', !isNaN(+entry.entryCount) ? renderNumber(entry.entryCount) : '?');
+ elem.text(text);
+
+ // https://github.com/gorhill/uBlock/issues/78
+ // Badge for non-secure connection
+ var remoteURL = listKey;
+ if ( remoteURL.lastIndexOf('http:', 0) !== 0 ) {
+ remoteURL = entry.homeURL || '';
}
- // Update status
- if ( hostsEntry.off !== true ) {
- var obsolete = asset.repoObsolete ||
- asset.cacheObsolete ||
- asset.cached !== true && re3rdPartyExternalAsset.test(listKey);
- if ( obsolete ) {
- html.push(
- ' ',
- '',
- asset.repoObsolete ? updateButtontext : obsoleteButtontext,
- ''
- );
+ if ( remoteURL.lastIndexOf('http:', 0) === 0 ) {
+ li.descendants('span.status.unsecure').css('display', '');
+ }
+
+ // https://github.com/chrisaljoudi/uBlock/issues/104
+ var asset = listDetails.cache[listKey] || {};
+
+ // Badge for update status
+ if ( entry.off !== true ) {
+ if ( asset.repoObsolete ) {
+ li.descendants('span.status.new').css('display', '');
+ needUpdate = true;
+ } else if ( asset.cacheObsolete ) {
+ li.descendants('span.status.obsolete').css('display', '');
+ needUpdate = true;
+ } else if ( entry.external && !asset.cached ) {
+ li.descendants('span.status.obsolete').css('display', '');
needUpdate = true;
}
}
+
// In cache
if ( asset.cached ) {
- html.push(
- ' ',
- '',
- purgeButtontext,
- ''
- );
+ elem = li.descendants('span.status.purge');
+ elem.css('display', '');
+ elem.attr('title', lastUpdateString.replace('{{ago}}', renderElapsedTimeToString(asset.lastModified)));
hasCachedContent = true;
}
- return html.join('\n');
+ return li;
};
var onListsReceived = function(details) {
@@ -161,34 +152,37 @@ var renderBlacklists = function() {
needUpdate = false;
hasCachedContent = false;
- // Visually split the filter lists in two groups: built-in and external
- var htmlBuiltin = [];
- var htmlExternal = [];
- var hostsPaths = Object.keys(details.available);
- var hostsPath, hostsEntry;
- for ( var i = 0; i < hostsPaths.length; i++ ) {
- hostsPath = hostsPaths[i];
- hostsEntry = details.available[hostsPath];
- if ( hostsEntry.external ) {
- htmlExternal.push(htmlFromLeaf(hostsPath, hostsEntry));
- } else {
- htmlBuiltin.push(htmlFromLeaf(hostsPath, hostsEntry));
+ var availableLists = details.available;
+ var listKeys = Object.keys(details.available);
+ listKeys.sort(function(a, b) {
+ var ta = availableLists[a].title || '';
+ var tb = availableLists[b].title || '';
+ if ( ta !== '' && tb !== '' ) {
+ return ta.localeCompare(tb);
}
+ if ( ta === '' && tb === '' ) {
+ return a.localeCompare(b);
+ }
+ if ( tb === '' ) {
+ return -1;
+ }
+ return 1;
+ });
+ var ulList = uDom('#lists').empty();
+ for ( var i = 0; i < listKeys.length; i++ ) {
+ ulList.append(liFromListEntry(listKeys[i]));
}
- if ( htmlExternal.length !== 0 ) {
- htmlBuiltin.push(' ');
- }
- var html = htmlBuiltin.concat(htmlExternal);
uDom('#listsOfBlockedHostsPrompt').text(
- vAPI.i18n('hostsFilesStats')
- .replace('{{blockedHostnameCount}}', details.blockedHostnameCount.toLocaleString())
+ vAPI.i18n('hostsFilesStats').replace(
+ '{{blockedHostnameCount}}',
+ renderNumber(details.blockedHostnameCount)
+ )
);
uDom('#autoUpdate').prop('checked', listDetails.autoUpdate === true);
- uDom('#lists').html(html.join(''));
- uDom('a').attr('target', '_blank');
- updateWidgets();
+ renderWidgets();
+ renderBusyOverlay(details.manualUpdate, details.manualUpdateProgress);
};
messager.send({ what: 'getLists' }, onListsReceived);
@@ -196,6 +190,37 @@ 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() {
@@ -292,55 +317,70 @@ var onPurgeClicked = function() {
/******************************************************************************/
-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 selectHostsFiles = function(callback) {
var switches = [];
- var lis = uDom('#lists .listDetails');
+ var lis = uDom('#lists .listEntry'), li;
var i = lis.length;
- var path;
while ( i-- ) {
- path = lis
- .subset(i, 1)
- .descendants('a')
- .attr('href');
+ li = lis.at(i);
switches.push({
- location: path,
- off: lis.subset(i, 1).descendants('input').prop('checked') === false
+ location: li.descendants('a').attr('href'),
+ off: li.descendants('input').prop('checked') === false
});
}
+
messager.send({
- what: 'reloadHostsFiles',
- switches: switches,
- update: update
- });
- cacheWasPurged = false;
+ what: 'selectHostsFiles',
+ switches: switches
+ }, callback);
};
/******************************************************************************/
var buttonApplyHandler = function() {
- reloadAll(false);
- uDom('#buttonApply').toggleClass('enabled', false);
+ uDom('#buttonApply').removeClass('enabled');
+
+ renderBusyOverlay(true);
+
+ var onSelectionDone = function() {
+ messager.send({ what: 'reloadHostsFiles' });
+ };
+
+ selectHostsFiles(onSelectionDone);
+
+ cacheWasPurged = false;
};
/******************************************************************************/
var buttonUpdateHandler = function() {
+ uDom('#buttonUpdate').removeClass('enabled');
+
if ( needUpdate ) {
- reloadAll(true);
+ renderBusyOverlay(true);
+
+ var onSelectionDone = function() {
+ messager.send({ what: 'forceUpdateAssets' });
+ };
+
+ selectHostsFiles(onSelectionDone);
+
+ cacheWasPurged = false;
}
};
/******************************************************************************/
var buttonPurgeAllHandler = function() {
+ uDom('#buttonPurgeAll').removeClass('enabled');
+
+ renderBusyOverlay(true);
+
var onCompleted = function() {
- renderBlacklists();
+ cacheWasPurged = true;
+ renderHostsFiles();
};
+
messager.send({ what: 'purgeAllCaches' }, onCompleted);
};
@@ -382,7 +422,7 @@ var externalListsApplyHandler = function() {
name: 'externalHostsFiles',
value: externalHostsFiles
});
- renderBlacklists();
+ renderHostsFiles();
uDom('#externalListsParse').prop('disabled', true);
};
@@ -393,13 +433,13 @@ uDom.onLoad(function() {
uDom('#buttonApply').on('click', buttonApplyHandler);
uDom('#buttonUpdate').on('click', buttonUpdateHandler);
uDom('#buttonPurgeAll').on('click', buttonPurgeAllHandler);
- uDom('#lists').on('change', '.listDetails > input', onListCheckboxChanged);
- uDom('#lists').on('click', '.listDetails > a:nth-of-type(1)', onListLinkClicked);
+ uDom('#lists').on('change', '.listEntry > input', onListCheckboxChanged);
+ uDom('#lists').on('click', '.listEntry > a:nth-of-type(1)', onListLinkClicked);
uDom('#lists').on('click', 'span.purge', onPurgeClicked);
uDom('#externalHostsFiles').on('input', externalListsChangeHandler);
uDom('#externalListsParse').on('click', externalListsApplyHandler);
- renderBlacklists();
+ renderHostsFiles();
renderExternalLists();
});
diff --git a/src/js/httpsb.js b/src/js/httpsb.js
index 53cbea8..16b2ac2 100644
--- a/src/js/httpsb.js
+++ b/src/js/httpsb.js
@@ -27,6 +27,7 @@
var µm = µMatrix;
µm.pMatrix = new µm.Matrix();
µm.pMatrix.setSwitch('matrix-off', 'localhost', 1);
+ µm.pMatrix.setSwitch('matrix-off', 'about-scheme', 1);
µm.pMatrix.setSwitch('matrix-off', 'chrome-extension-scheme', 1);
µm.pMatrix.setSwitch('matrix-off', 'chrome-scheme', 1);
µm.pMatrix.setSwitch('matrix-off', µm.behindTheSceneScope, 1);
diff --git a/src/js/i18n.js b/src/js/i18n.js
index cac6e61..a83a9f5 100644
--- a/src/js/i18n.js
+++ b/src/js/i18n.js
@@ -19,29 +19,65 @@
Home: https://github.com/gorhill/uMatrix
*/
-// Helper to deal with the i18n'ing of HTML files.
-// jQuery must be present at this point.
-
-window.addEventListener('load', function() {
- 'use strict';
-
- var nodeList = document.querySelectorAll('[data-i18n]');
- var i = nodeList.length;
- var node;
- while ( i-- ) {
- node = nodeList[i];
- vAPI.insertHTML(node, vAPI.i18n(node.getAttribute('data-i18n')));
+/******************************************************************************/
+
+// This file should always be included at the end of the `body` tag, so as
+// to ensure all i18n targets are already loaded.
+
+(function() {
+
+'use strict';
+
+/******************************************************************************/
+
+var nodeList = document.querySelectorAll('[data-i18n]');
+var i = nodeList.length;
+var node;
+while ( i-- ) {
+ node = nodeList[i];
+ vAPI.insertHTML(node, vAPI.i18n(node.getAttribute('data-i18n')));
+}
+
+// copy text of if any to document title
+node = document.querySelector('h1');
+if ( node !== null ) {
+ document.title = node.textContent;
+}
+
+// Tool tips
+nodeList = document.querySelectorAll('[data-i18n-tip]');
+i = nodeList.length;
+while ( i-- ) {
+ node = nodeList[i];
+ node.setAttribute('data-tip', vAPI.i18n(node.getAttribute('data-i18n-tip')));
+}
+
+/******************************************************************************/
+
+vAPI.i18n.renderElapsedTimeToString = function(tstamp) {
+ var value = (Date.now() - tstamp) / 60000;
+ if ( value < 2 ) {
+ return vAPI.i18n('elapsedOneMinuteAgo');
+ }
+ if ( value < 60 ) {
+ return vAPI.i18n('elapsedManyMinutesAgo').replace('{{value}}', Math.floor(value).toLocaleString());
+ }
+ value /= 60;
+ if ( value < 2 ) {
+ return vAPI.i18n('elapsedOneHourAgo');
}
- // copy text of if any to document title
- node = document.querySelector('h1');
- if ( node !== null ) {
- document.title = node.textContent;
+ if ( value < 24 ) {
+ return vAPI.i18n('elapsedManyHoursAgo').replace('{{value}}', Math.floor(value).toLocaleString());
}
- // Tool tips
- nodeList = document.querySelectorAll('[data-i18n-tip]');
- i = nodeList.length;
- while ( i-- ) {
- node = nodeList[i];
- node.setAttribute('data-tip', vAPI.i18n(node.getAttribute('data-i18n-tip')));
+ value /= 24;
+ if ( value < 2 ) {
+ return vAPI.i18n('elapsedOneDayAgo');
}
-});
+ return vAPI.i18n('elapsedManyDaysAgo').replace('{{value}}', Math.floor(value).toLocaleString());
+};
+
+/******************************************************************************/
+
+})();
+
+/******************************************************************************/
diff --git a/src/js/messaging.js b/src/js/messaging.js
index cbe268f..3d4e708 100644
--- a/src/js/messaging.js
+++ b/src/js/messaging.js
@@ -55,6 +55,10 @@ function onMessage(request, sender, callback) {
µm.forceReload(request.tabId);
break;
+ case 'forceUpdateAssets':
+ µm.assetUpdater.force();
+ break;
+
case 'getUserSettings':
response = µm.userSettings;
break;
@@ -68,7 +72,11 @@ function onMessage(request, sender, callback) {
break;
case 'reloadHostsFiles':
- µm.reloadHostsFiles(request.switches, request.update);
+ µm.reloadHostsFiles();
+ break;
+
+ case 'selectHostsFiles':
+ µm.selectHostsFiles(request.switches);
break;
case 'userSettings':
@@ -637,20 +645,41 @@ var µm = µMatrix;
/******************************************************************************/
+var prepEntries = function(entries) {
+ var µmuri = µm.URI;
+ var entry;
+ for ( var k in entries ) {
+ if ( entries.hasOwnProperty(k) === false ) {
+ continue;
+ }
+ entry = entries[k];
+ if ( typeof entry.homeURL === 'string' ) {
+ entry.homeHostname = µmuri.hostnameFromURI(entry.homeURL);
+ entry.homeDomain = µmuri.domainFromHostname(entry.homeHostname);
+ }
+ }
+};
+
+/******************************************************************************/
+
var getLists = function(callback) {
var r = {
+ autoUpdate: µm.userSettings.autoUpdate,
available: null,
cache: null,
current: µm.liveHostsFiles,
- blockedHostnameCount: µm.ubiquitousBlacklist.count,
- autoUpdate: µm.userSettings.autoUpdate
+ blockedHostnameCount: µm.ubiquitousBlacklist.count
};
var onMetadataReady = function(entries) {
r.cache = entries;
+ prepEntries(r.cache);
+ r.manualUpdate = µm.assetUpdater.manualUpdate;
+ r.manualUpdateProgress = µm.assetUpdater.manualUpdateProgress;
callback(r);
};
var onAvailableHostsFilesReady = function(lists) {
r.available = lists;
+ prepEntries(r.available);
µm.assets.metadata(onMetadataReady);
};
µm.getAvailableHostsFiles(onAvailableHostsFilesReady);
diff --git a/src/js/start.js b/src/js/start.js
index 864cc94..9173a3f 100644
--- a/src/js/start.js
+++ b/src/js/start.js
@@ -70,18 +70,12 @@
(function() {
var µm = µMatrix;
- var jobDone = function(details) {
- if ( details.changedCount === 0 ) {
- return;
- }
- µm.loadUpdatableAssets();
- };
-
- var jobCallback = function() {
- µm.assetUpdater.update(null, jobDone);
- };
-
- µm.asyncJobs.add('autoUpdateAssets', null, jobCallback, µm.updateAssetsEvery, true);
+ // https://github.com/chrisaljoudi/uBlock/issues/184
+ // Check for updates not too far in the future.
+ µm.assetUpdater.onStart.addListener(µm.updateStartHandler.bind(µm));
+ µm.assetUpdater.onCompleted.addListener(µm.updateCompleteHandler.bind(µm));
+ µm.assetUpdater.onAssetUpdated.addListener(µm.assetUpdatedHandler.bind(µm));
+ µm.assets.onAssetCacheRemoved.addListener(µm.assetCacheRemovedHandler.bind(µm));
})();
/******************************************************************************/
@@ -91,6 +85,8 @@
(function() {
var µm = µMatrix;
+ µm.assets.remoteFetchBarrier += 1;
+
// This needs to be done when the PSL is loaded
var bindTabs = function(tabs) {
var tab;
@@ -102,13 +98,27 @@
µm.bindTabToPageStats(tab.id);
}
µm.webRequest.start();
+
+ // Important: remove barrier to remote fetching, this was useful only
+ // for launch time.
+ µm.assets.remoteFetchBarrier -= 1;
};
var queryTabs = function() {
vAPI.tabs.getAll(bindTabs);
};
- µm.load(queryTabs);
+ var onSettingsReady = function(settings) {
+ µm.loadPublicSuffixList(queryTabs);
+ µm.loadHostsFiles();
+ };
+
+ var onMatrixReady = function() {
+ };
+
+ µm.loadUserSettings(onSettingsReady);
+ µm.loadMatrix(onMatrixReady);
+
})();
/******************************************************************************/
diff --git a/src/js/storage.js b/src/js/storage.js
index 83e3017..356ac6e 100644
--- a/src/js/storage.js
+++ b/src/js/storage.js
@@ -19,6 +19,7 @@
Home: https://github.com/gorhill/uMatrix
*/
+/* jshint boss: true */
/* global chrome, µMatrix, punycode, publicSuffixList */
/******************************************************************************/
@@ -250,7 +251,7 @@
/******************************************************************************/
µMatrix.mergeHostsFile = function(details) {
- // console.log('storage.js > mergeHostsFile from "%s": "%s..."', details.path, details.content.slice(0, 40));
+ //console.log('storage.js > mergeHostsFile from "%s": "%s..."', details.path, details.content.slice(0, 40));
var usedCount = this.ubiquitousBlacklist.count;
var duplicateCount = this.ubiquitousBlacklist.duplicateCount;
@@ -268,7 +269,7 @@
/******************************************************************************/
µMatrix.mergeHostsFileContent = function(rawText) {
- // console.log('storage.js > mergeHostsFileContent from "%s": "%s..."', details.path, details.content.slice(0, 40));
+ //console.log('storage.js > mergeHostsFileContent from "%s": "%s..."', details.path, details.content.slice(0, 40));
var rawEnd = rawText.length;
var ubiquitousBlacklist = this.ubiquitousBlacklist;
@@ -312,7 +313,7 @@
// For example, when a filter contains whitespace characters, or
// whatever else outside the range of printable ascii characters.
if ( matches[0] !== line ) {
- // console.error('"%s": "%s" !== "%s"', details.path, matches[0], line);
+ //console.error('"%s": "%s" !== "%s"', details.path, matches[0], line);
continue;
}
@@ -327,26 +328,50 @@
/******************************************************************************/
-// `switches` contains the preset blacklists for which the switch must be
-// revisited.
+// `switches` contains the filter lists for which the switch must be revisited.
-µMatrix.reloadHostsFiles = function(switches, update) {
- var liveHostsFiles = this.liveHostsFiles;
+µMatrix.selectHostsFiles = function(switches) {
+ switches = switches || {};
- // Toggle switches
+ // Only the lists referenced by the switches are touched.
+ var liveHostsFiles = this.liveHostsFiles;
+ var entry, state, location;
var i = switches.length;
while ( i-- ) {
- if ( !liveHostsFiles[switches[i].location] ) {
+ entry = switches[i];
+ state = entry.off === true;
+ location = entry.location;
+ if ( liveHostsFiles.hasOwnProperty(location) === false ) {
+ if ( state !== true ) {
+ liveHostsFiles[location] = { off: state };
+ }
+ continue;
+ }
+ if ( liveHostsFiles[location].off === state ) {
continue;
}
- liveHostsFiles[switches[i].location].off = !!switches[i].off;
+ liveHostsFiles[location].off = state;
}
- // Save switch states
- vAPI.storage.set(
- { 'liveHostsFiles': liveHostsFiles },
- this.loadUpdatableAssets.bind(this, update)
- );
+ vAPI.storage.set({ 'liveHostsFiles': liveHostsFiles });
+};
+
+/******************************************************************************/
+
+// `switches` contains the preset blacklists for which the switch must be
+// revisited.
+
+µMatrix.reloadHostsFiles = function() {
+ var µm = this;
+
+ // We are just reloading the filter lists: we do not want assets to update.
+ this.assets.autoUpdate = false;
+
+ var onHostsFilesReady = function() {
+ µm.assets.autoUpdate = µm.userSettings.autoUpdate;
+ };
+
+ this.loadHostsFiles(onHostsFilesReady);
};
/******************************************************************************/
@@ -362,86 +387,115 @@
}
callback();
};
+
this.assets.get(this.pslPath, applyPublicSuffixList);
};
/******************************************************************************/
-// Load updatable assets
+µMatrix.updateStartHandler = function(callback) {
+ var µm = this;
+ var onListsReady = function(lists) {
+ var assets = {};
+ for ( var location in lists ) {
+ if ( lists.hasOwnProperty(location) === false ) {
+ continue;
+ }
+ if ( lists[location].off ) {
+ continue;
+ }
+ assets[location] = true;
+ }
+ assets[µm.pslPath] = true;
+ callback(assets);
+ };
-µMatrix.loadUpdatableAssets = function(forceUpdate, callback) {
- if ( typeof callback !== 'function' ) {
- callback = this.noopFunc;
- }
+ this.getAvailableHostsFiles(onListsReady);
+};
- this.assets.autoUpdate = forceUpdate === true;
- this.assets.autoUpdateDelay = this.updateAssetsEvery;
- if ( forceUpdate ) {
- this.updater.restart();
- }
+/******************************************************************************/
- this.loadPublicSuffixList(callback);
- this.loadHostsFiles();
+µMatrix.assetUpdatedHandler = function(details) {
+ var path = details.path || '';
+ if ( this.liveHostsFiles.hasOwnProperty(path) === false ) {
+ return;
+ }
+ var entry = this.liveHostsFiles[path];
+ if ( entry.off ) {
+ return;
+ }
+ // Compile the list while we have the raw version in memory
+ //console.debug('µMatrix.getCompiledFilterList/onRawListLoaded: compiling "%s"', path);
+ //this.assets.put(
+ // this.getCompiledFilterListPath(path),
+ // this.compileFilters(details.content)
+ //);
};
/******************************************************************************/
-// Load all
+µMatrix.updateCompleteHandler = function(details) {
+ var µm = this;
-µMatrix.load = function(callback) {
- if ( typeof callback !== 'function' ) {
- callback = this.noopFunc;
+ var updatedCount = details.updatedCount;
+ if ( updatedCount === 0 ) {
+ return;
}
- var µm = this;
- var settingsReady = false;
- var matrixReady = false;
+ // Assets are supposed to have been all updated, prevent fetching from
+ // remote servers.
+ µm.assets.remoteFetchBarrier += 1;
- // TODO: to remove when everybody (and their backup file) has their
- // ua-spoof/referrer-spoof setting converted into a matrix switch.
- var onSettingsAndMatrixReady = function() {
- if ( !settingsReady || !matrixReady ) {
- return;
- }
- var saveMatrix = false;
- if ( µm.userSettings.spoofUserAgent ) {
- µm.tMatrix.setSwitch('ua-spoof', '*', 1);
- µm.pMatrix.setSwitch('ua-spoof', '*', 1);
- saveMatrix = true;
- }
- if ( µm.userSettings.processReferer ) {
- µm.tMatrix.setSwitch('referrer-spoof', '*', 1);
- µm.pMatrix.setSwitch('referrer-spoof', '*', 1);
- saveMatrix = true;
- }
- if ( saveMatrix ) {
- µm.saveMatrix();
- }
- delete µm.userSettings.processReferer;
- delete µm.userSettings.spoofUserAgent;
- µm.saveUserSettings();
- µm.XAL.keyvalRemoveOne('processReferer');
- µm.XAL.keyvalRemoveOne('spoofUserAgent');
+ var onFiltersReady = function() {
+ µm.assets.remoteFetchBarrier -= 1;
};
- var onSettingsReady = function(settings) {
- // Never auto-update at boot time
- µm.loadUpdatableAssets(false, callback);
-
- // Setup auto-updater, earlier if auto-upate is enabled, later if not
- if ( settings.autoUpdate ) {
- µm.updater.restart(µm.firstUpdateAfter);
+ var onPSLReady = function() {
+ if ( updatedCount !== 0 ) {
+ //console.debug('storage.js > µMatrix.updateCompleteHandler: reloading filter lists');
+ µm.loadHostsFiles(onFiltersReady);
+ } else {
+ onFiltersReady();
}
- settingsReady = true;
- onSettingsAndMatrixReady();
};
- var onMatrixReady = function() {
- matrixReady = true;
- onSettingsAndMatrixReady();
+ if ( details.hasOwnProperty(this.pslPath) ) {
+ //console.debug('storage.js > µMatrix.updateCompleteHandler: reloading PSL');
+ this.loadPublicSuffixList(onPSLReady);
+ updatedCount -= 1;
+ } else {
+ onPSLReady();
+ }
+};
+
+/******************************************************************************/
+
+µMatrix.assetCacheRemovedHandler = (function() {
+ var barrier = false;
+
+ var handler = function(paths) {
+ if ( barrier ) {
+ return;
+ }
+ barrier = true;
+ var i = paths.length;
+ var path;
+ while ( i-- ) {
+ path = paths[i];
+ if ( this.liveHostsFiles.hasOwnProperty(path) ) {
+ //console.debug('µMatrix.assetCacheRemovedHandler: decompiling "%s"', path);
+ //this.purgeCompiledFilterList(path);
+ continue;
+ }
+ if ( path === this.pslPath ) {
+ //console.debug('µMatrix.assetCacheRemovedHandler: decompiling "%s"', path);
+ //this.assets.purge('cache://compiled-publicsuffixlist');
+ continue;
+ }
+ }
+ //this.destroySelfie();
+ barrier = false;
};
- this.loadUserSettings(onSettingsReady);
- this.loadMatrix(onMatrixReady);
- this.getBytesInUse();
-};
+ return handler;
+})();