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; +})();