123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405 |
- /*******************************************************************************
- ηMatrix - a browser extension to black/white list requests.
- Copyright (C) 2014-2019 Raymond Hill
- Copyright (C) 2019-2022 Alessio Vanni
- 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
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see {http://www.gnu.org/licenses/}.
- Home: https://gitlab.com/vannilla/ematrix
- uMatrix Home: https://github.com/gorhill/uMatrix
- */
- 'use strict';
- (function () {
- let listDetails = {};
- let lastUpdateTemplateString = vAPI.i18n('hostsFilesLastUpdate');
- let hostsFilesSettingsHash;
- let reValidExternalList = /[a-z-]+:\/\/\S*\/\S+/;
- vAPI.messaging.addListener(function (msg) {
- switch (msg.what) {
- case 'assetUpdated':
- updateAssetStatus(msg);
- break;
- case 'assetsUpdated':
- document.body.classList.remove('updating');
- break;
- case 'loadHostsFilesCompleted':
- renderHostsFiles();
- break;
- default:
- break;
- }
- });
- let renderNumber = function (value) {
- return value.toLocaleString();
- };
- let renderHostsFiles = function (soft) {
- let listEntryTemplate = uDom('#templates .listEntry');
- let listStatsTemplate = vAPI.i18n('hostsFilesPerFileStats');
- let renderETTS = vAPI.i18n.renderElapsedTimeToString;
- let reExternalHostFile = /^https?:/;
- // Assemble a pretty list name if possible
- let listNameFromListKey = function (listKey) {
- let list =
- listDetails.current[listKey] || listDetails.available[listKey];
- let listTitle = list ? list.title : '';
- if (listTitle === '') {
- return listKey;
- }
- return listTitle;
- };
- let liFromListEntry = function (listKey, li) {
- let entry = listDetails.available[listKey];
- let elem;
- if (!li) {
- li = listEntryTemplate.clone().nodeAt(0);
- }
- if (li.getAttribute('data-listkey') !== listKey) {
- li.setAttribute('data-listkey', listKey);
- elem = li.querySelector('input[type="checkbox"]');
- elem.checked = (entry.off !== true);
- elem = li.querySelector('a:nth-of-type(1)');
- elem.setAttribute('href',
- 'asset-viewer.html?url=' + encodeURI(listKey));
- elem.setAttribute('type', 'text/html');
- elem.textContent = listNameFromListKey(listKey);
- li.classList.remove('toRemove');
- if (entry.supportName) {
- li.classList.add('support');
- elem = li.querySelector('a.support');
- elem.setAttribute('href', entry.supportURL);
- elem.setAttribute('title', entry.supportName);
- } else {
- li.classList.remove('support');
- }
- if (entry.external) {
- li.classList.add('external');
- } else {
- li.classList.remove('external');
- }
- if (entry.instructionURL) {
- li.classList.add('mustread');
- elem = li.querySelector('a.mustread');
- elem.setAttribute('href', entry.instructionURL);
- } else {
- li.classList.remove('mustread');
- }
- }
- // https://github.com/gorhill/uBlock/issues/1429
- if (!soft) {
- elem = li.querySelector('input[type="checkbox"]');
- elem.checked = entry.off !== true;
- }
- elem = li.querySelector('span.counts');
- let text = '';
- if (!isNaN(+entry.entryUsedCount) && !isNaN(+entry.entryCount)) {
- text = listStatsTemplate
- .replace('{{used}}',
- renderNumber(entry.off ? 0 : entry.entryUsedCount))
- .replace('{{total}}',
- renderNumber(entry.entryCount));
- }
- elem.textContent = text;
- // https://github.com/chrisaljoudi/uBlock/issues/104
- let asset = listDetails.cache[listKey] || {};
- let remoteURL = asset.remoteURL;
- li.classList.toggle('unsecure',
- typeof remoteURL === 'string'
- && remoteURL.lastIndexOf('http:', 0) === 0);
- li.classList.toggle('failed', asset.error !== undefined);
- li.classList.toggle('obsolete', asset.obsolete === true);
- li.classList.toggle('cached',
- asset.cached === true && asset.writeTime > 0);
- if (asset.cached) {
- li.querySelector('.status.cache')
- .setAttribute('title',
- lastUpdateTemplateString
- .replace('{{ago}}',
- renderETTS(asset.writeTime)));
- }
- li.classList.remove('discard');
- return li;
- };
- let onListsReceived = function (details) {
- // Before all, set context vars
- listDetails = details;
- // Incremental rendering: this will allow us to easily discard unused
- // DOM list entries.
- uDom('#lists .listEntry').addClass('discard');
- let availableLists = details.available;
- let listKeys = Object.keys(details.available);
- // Sort works this way:
- // - Send /^https?:/ items at the end (custom hosts file URL)
- listKeys.sort(function (a, b) {
- let ta = availableLists[a].title || a;
- let tb = availableLists[b].title || b;
- let ca = reExternalHostFile.test(ta);
- let cb = reExternalHostFile.test(tb);
- if (ca === cb) {
- return ta.localeCompare(tb);
- }
- return (cb) ? -1 : 1;
- });
- let ulList = document.querySelector('#lists');
- for (let i=0; i<listKeys.length; ++i) {
- let liEntry = liFromListEntry(listKeys[i], ulList.children[i]);
- if (liEntry.parentElement === null) {
- ulList.appendChild(liEntry);
- }
- }
- uDom('#lists .listEntry.discard').remove();
- uDom('#listsOfBlockedHostsPrompt')
- .text(vAPI.i18n('hostsFilesStats')
- .replace('{{blockedHostnameCount}}',
- renderNumber(details.blockedHostnameCount)));
- uDom('#autoUpdate').prop('checked',
- listDetails.autoUpdate === true);
- if (!soft) {
- hostsFilesSettingsHash = hashFromCurrentFromSettings();
- }
- renderWidgets();
- };
- vAPI.messaging.send('hosts-files.js', {
- what: 'getLists'
- }, onListsReceived);
- };
- let renderWidgets = function () {
- let sel1 =
- 'body:not(.updating) #lists .listEntry.obsolete '
- + '> input[type="checkbox"]:checked';
- let sel2 = '#lists .listEntry.cached';
- uDom('#buttonUpdate')
- .toggleClass('disabled', document.querySelector(sel1) === null);
- uDom('#buttonPurgeAll')
- .toggleClass('disabled', document.querySelector(sel2) === null);
- uDom('#buttonApply')
- .toggleClass('disabled',
- hostsFilesSettingsHash ===
- hashFromCurrentFromSettings());
- };
- let updateAssetStatus = function (details) {
- let li = document
- .querySelector('#lists .listEntry[data-listkey="'+details.key+'"]');
- if (li === null) {
- return;
- }
- li.classList.toggle('failed', !!details.failed);
- li.classList.toggle('obsolete', !details.cached);
- li.classList.toggle('cached', !!details.cached);
- if (details.cached) {
- let str = vAPI.i18n.renderElapsedTimeToString(Date.now());
- li.querySelector('.status.cache')
- .setAttribute('title',
- lastUpdateTemplateString.replace('{{ago}}', str));
- }
- renderWidgets();
- };
- /**
- Compute a hash from all the settings affecting how filter lists are loaded
- in memory.
- **/
- let hashFromCurrentFromSettings = function () {
- let hash = [];
- let listHash = [];
- let sel = '#lists .listEntry[data-listkey]:not(.toRemove)';
- let ext = 'externalHostsFiles';
- let listEntries = document.querySelectorAll(sel);
- for (let i=listEntries.length-1; i>=0; --i) {
- let li = listEntries[i];
- if (li.querySelector('input[type="checkbox"]:checked') !== null) {
- listHash.push(li.getAttribute('data-listkey'));
- }
- }
- hash.push(listHash.sort().join(),
- reValidExternalList.test(document.getElementById(ext).value),
- document.querySelector('#lists .listEntry.toRemove') !== null);
- return hash.join();
- };
- let onHostsFilesSettingsChanged = function () {
- renderWidgets();
- };
- let onRemoveExternalHostsFile = function (ev) {
- let liEntry = uDom(this).ancestors('[data-listkey]');
- let listKey = liEntry.attr('data-listkey');
- if (listKey) {
- liEntry.toggleClass('toRemove');
- renderWidgets();
- }
- ev.preventDefault();
- };
- let onPurgeClicked = function () {
- let button = uDom(this);
- let liEntry = button.ancestors('[data-listkey]');
- let listKey = liEntry.attr('data-listkey');
- if (!listKey) {
- return;
- }
- vAPI.messaging.send('hosts-files.js', {
- what: 'purgeCache',
- assetKey: listKey
- });
- liEntry.addClass('obsolete');
- liEntry.removeClass('cached');
- if (liEntry.descendants('input').first().prop('checked')) {
- renderWidgets();
- }
- };
- let selectHostsFiles = function (callback) {
- // Hosts files to select
- let toSelect = [];
- let sel = '#lists .listEntry[data-listkey]:not(.toRemove)';
- let sel2 = '#lists .listEntry.toRemove[data-listkey]';
- let liEntries = document.querySelectorAll(sel);
- for (let i=liEntries.length-1; i>=0; --i) {
- let li = liEntries[i];
- if (li.querySelector('input[type="checkbox"]:checked') !== null) {
- toSelect.push(li.getAttribute('data-listkey'));
- }
- }
- // External hosts files to remove
- let toRemove = [];
- liEntries = document.querySelectorAll(sel2);
- for (let i=liEntries.length-1; i>=0; --i) {
- toRemove.push(liEntries[i].getAttribute('data-listkey'));
- }
- // External hosts files to import
- let externalListsElem = document.getElementById('externalHostsFiles');
- let toImport = externalListsElem.value.trim();
- externalListsElem.value = '';
- vAPI.messaging.send('hosts-files.js', {
- what: 'selectHostsFiles',
- toSelect: toSelect,
- toImport: toImport,
- toRemove: toRemove
- }, callback);
- hostsFilesSettingsHash = hashFromCurrentFromSettings();
- };
- let buttonApplyHandler = function () {
- uDom('#buttonApply').removeClass('enabled');
- selectHostsFiles(function () {
- vAPI.messaging.send('hosts-files.js', {
- what: 'reloadHostsFiles'
- });
- });
- renderWidgets();
- };
- let buttonUpdateHandler = function () {
- uDom('#buttonUpdate').removeClass('enabled');
- selectHostsFiles(function () {
- document.body.classList.add('updating');
- vAPI.messaging.send('hosts-files.js', {
- what: 'forceUpdateAssets'
- });
- renderWidgets();
- });
- renderWidgets();
- };
- let buttonPurgeAllHandler = function () {
- uDom('#buttonPurgeAll').removeClass('enabled');
- vAPI.messaging.send('hosts-files.js', {
- what: 'purgeAllCaches'
- }, function () {
- renderHostsFiles(true);
- });
- };
- let autoUpdateCheckboxChanged = function () {
- vAPI.messaging.send('hosts-files.js', {
- what: 'userSettings',
- name: 'autoUpdate',
- value: this.checked
- });
- };
- uDom('#autoUpdate').on('change', autoUpdateCheckboxChanged);
- uDom('#buttonApply').on('click', buttonApplyHandler);
- uDom('#buttonUpdate').on('click', buttonUpdateHandler);
- uDom('#buttonPurgeAll').on('click', buttonPurgeAllHandler);
- uDom('#lists').on('change', '.listEntry > input',
- onHostsFilesSettingsChanged);
- uDom('#lists').on('click', '.listEntry > a.remove',
- onRemoveExternalHostsFile);
- uDom('#lists').on('click', 'span.cache', onPurgeClicked);
- uDom('#externalHostsFiles').on('input', onHostsFilesSettingsChanged);
- renderHostsFiles();
- })();
|