mirror of
https://github.com/gorhill/uMatrix.git
synced 2024-09-28 15:21:49 +12:00
first draft toward fixing #30
This commit is contained in:
parent
bf4f3e71d3
commit
2cf4a57bf4
19 changed files with 1084 additions and 428 deletions
|
@ -32,7 +32,7 @@
|
|||
"description": "a tab in dashboard"
|
||||
},
|
||||
"ubiquitousRulesPageName" : {
|
||||
"message": "Hosts files",
|
||||
"message": "Assets",
|
||||
"description": "a tab in dashboard"
|
||||
},
|
||||
"rawSettingsPageName": {
|
||||
|
@ -479,6 +479,10 @@
|
|||
},
|
||||
|
||||
|
||||
"assetsHostsSection" : {
|
||||
"message": "Hosts files",
|
||||
"description": "header to identify the hosts files section"
|
||||
},
|
||||
"hostsFilesPrompt" : {
|
||||
"message": "All hostnames in a hosts file are loaded as blacklisted hostnames in the global scope.",
|
||||
"description": ""
|
||||
|
@ -500,7 +504,7 @@
|
|||
"description": ""
|
||||
},
|
||||
"hostsFilesAutoUpdatePrompt":{
|
||||
"message":"Auto-update hosts files.",
|
||||
"message":"Auto-update assets.",
|
||||
"description":""
|
||||
},
|
||||
"hostsFilesUpdateNow":{
|
||||
|
@ -512,7 +516,7 @@
|
|||
"description":""
|
||||
},
|
||||
"hostsFilesExternalListsHint":{
|
||||
"message":"One URL per line. Lines prefixed with ‘#’ will be ignored. Invalid URLs will be silently ignored.",
|
||||
"message":"Import external assets here:\nOne URL per line; invalid URLs will be silently ignored.",
|
||||
"description":""
|
||||
},
|
||||
"hostsFilesExternalListsParse":{
|
||||
|
@ -531,6 +535,18 @@
|
|||
"message":"outdated",
|
||||
"description":""
|
||||
},
|
||||
"assetsRecipesSection" : {
|
||||
"message": "Ruleset recipes",
|
||||
"description": "header to identify the ruleset files section"
|
||||
},
|
||||
"assetsRecipesSummary" : {
|
||||
"message": "Ruleset recipes are imported from the popup panel <em>on demand</em>, i.e. <b>only</b> through user interaction.",
|
||||
"description": ""
|
||||
},
|
||||
"assetsImportLabel" : {
|
||||
"message": "Import...",
|
||||
"description": ""
|
||||
},
|
||||
|
||||
"rawSettingsWarning" : {
|
||||
"message": "Warning! Change these raw configuration settings at your own risk.",
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<script src="js/usersettings.js"></script>
|
||||
<script src="js/liquid-dict.js"></script>
|
||||
<script src="js/matrix.js"></script>
|
||||
<script src="js/recipe-manager.js"></script>
|
||||
<script src="js/utils.js"></script>
|
||||
<script src="js/assets.js"></script>
|
||||
<script src="js/httpsb.js"></script>
|
||||
|
|
|
@ -3,7 +3,7 @@ body {
|
|||
color: #000;
|
||||
margin: 0;
|
||||
padding: 0 0.5em 0 0.5em;
|
||||
font: 15px/1.4 sans-serif;
|
||||
font: 14px/1.4 sans-serif;
|
||||
}
|
||||
body > *:first-child {
|
||||
margin-top: 0;
|
||||
|
|
|
@ -18,6 +18,19 @@ ul#options {
|
|||
ul#options li {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.assets {
|
||||
border: 1px solid #ccc;
|
||||
margin: 0.5em 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
.assets > div:first-of-type {
|
||||
background-color: #eee;
|
||||
margin: 0;
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
.assets > div + div {
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
ul#lists {
|
||||
margin: 0.5em 0 0 0;
|
||||
padding: 0;
|
||||
|
@ -40,7 +53,6 @@ li.listEntry > * {
|
|||
unicode-bidi: embed;
|
||||
}
|
||||
li.listEntry > a:nth-of-type(2) {
|
||||
font-size: 13px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
li.listEntry.toRemove > input[type="checkbox"] {
|
||||
|
@ -59,9 +71,12 @@ li.listEntry > .fa {
|
|||
li.listEntry > a.fa:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
li.listEntry.support > a.support {
|
||||
li.listEntry > a.support {
|
||||
display: inline-block;
|
||||
}
|
||||
li.listEntry > a.support[href=""] {
|
||||
display: none;
|
||||
}
|
||||
li.listEntry > a.remove,
|
||||
li.listEntry > a.remove:visited {
|
||||
color: darkred;
|
||||
|
@ -124,19 +139,20 @@ body.updating li.listEntry.obsolete > input[type="checkbox"]:checked ~ span.upda
|
|||
.dim {
|
||||
opacity: 0.5;
|
||||
}
|
||||
#externalLists {
|
||||
margin: 2em auto 0 auto;
|
||||
li.listEntry.importURL > input[type="checkbox"] ~ textarea {
|
||||
display: none;
|
||||
margin-left: 1.6em;
|
||||
}
|
||||
body[dir="ltr"] #externalListsDiv {
|
||||
margin-left: 1em;
|
||||
li.listEntry.importURL > input[type="checkbox"]:checked ~ textarea {
|
||||
display: block;
|
||||
}
|
||||
body[dir="rtl"] #externalListsDiv {
|
||||
margin-right: 1em;
|
||||
}
|
||||
#externalHostsFiles {
|
||||
li.listEntry.importURL > textarea {
|
||||
border: 1px solid #ddd;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
font-size: smaller;
|
||||
width: 100%;
|
||||
height: 12em;
|
||||
width: calc(100% - 4em);
|
||||
height: 6em;
|
||||
resize: none;
|
||||
white-space: pre;
|
||||
}
|
||||
|
|
|
@ -210,10 +210,6 @@ body .toolbar button.fa {
|
|||
display: block;
|
||||
}
|
||||
|
||||
#buttonReload {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
button > span.badge {
|
||||
padding: 1px 1px;
|
||||
display: inline-block;
|
||||
|
@ -232,66 +228,66 @@ button.disabled > span.badge {
|
|||
left: 10vw;
|
||||
width: 80vw;
|
||||
}
|
||||
.presetInfo {
|
||||
margin: 0.25em 0.5em;
|
||||
text-align: center;
|
||||
#dropDownMenuRecipes > .dropdown-menu > ul {
|
||||
max-height: 70vh;
|
||||
min-width: 50vw;
|
||||
overflow: auto;
|
||||
}
|
||||
.presetEntry {
|
||||
margin: 0.25em 0.25em;
|
||||
border-radius: 3px;
|
||||
padding: 0.5em;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
background-color: #eee;
|
||||
}
|
||||
.presetEntry .fa {
|
||||
margin-right: 0.25em;
|
||||
font-size: 110%;
|
||||
}
|
||||
.presetEntry:hover {
|
||||
background-color: #80e2ff;
|
||||
}
|
||||
#presetMore > *:first-child {
|
||||
margin: 0;
|
||||
#dropDownMenuRecipes li > ul {
|
||||
margin-left: 1em;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
color: #888;
|
||||
}
|
||||
.recipe {
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
list-style-type: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#presetMore > *:first-child + div {
|
||||
margin: 0.25em 0 0 0;
|
||||
padding: 0.25em 0 0 0;
|
||||
display: none;
|
||||
text-align: center;
|
||||
.recipe > div {
|
||||
align-items: baseline;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#presetMore > *:first-child + div.show {
|
||||
display: block;
|
||||
}
|
||||
#presetMore > *:first-child + div > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
#presetMoreRecipe {
|
||||
border: 1px solid #aaa;
|
||||
width: 75%;
|
||||
height: 4em;
|
||||
overflow: hidden;
|
||||
resize: none;
|
||||
font-size: 10px;
|
||||
.recipe > div > span {
|
||||
color: #888;
|
||||
}
|
||||
#presetMoreRecipe.bad {
|
||||
border: 1px solid #fcc;
|
||||
color: #aaa;
|
||||
.recipe > div > span:hover {
|
||||
color: #000;
|
||||
}
|
||||
#presetMoreWrite.bad {
|
||||
visibility: hidden;
|
||||
.recipe .expander {
|
||||
display: inline-block;
|
||||
padding: 0.4em;
|
||||
width: 0.8em;
|
||||
}
|
||||
|
||||
/* I think this is obsolete */
|
||||
.dropdown-menu > li > a > i {
|
||||
padding: 0 6px;
|
||||
font-size: 1.2em;
|
||||
.recipe .expander::before {
|
||||
content: '\2BC8';
|
||||
}
|
||||
.recipe.expanded .expander::before {
|
||||
content: '\2BC6';
|
||||
}
|
||||
.recipe .name {
|
||||
color: #000;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.recipe .committer {
|
||||
display: none;
|
||||
font-size: 120%;
|
||||
padding: 0.4em;
|
||||
width: 0.8em;
|
||||
}
|
||||
.recipe.mustCommit .committer {
|
||||
display: inline;
|
||||
}
|
||||
.recipe:hover {
|
||||
background-color: #eef;
|
||||
}
|
||||
.recipe .ruleset {
|
||||
display: none;
|
||||
font: smaller monospace;
|
||||
padding: 0 0.5em 0.5em 2em;
|
||||
white-space: pre;
|
||||
}
|
||||
.recipe.expanded .ruleset {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body .toolbar .scope {
|
||||
|
@ -347,7 +343,7 @@ body .toolbar #specificScope > span:first-of-type {
|
|||
body .toolbar #globalScope {
|
||||
justify-content: center;
|
||||
margin-left: 1px;
|
||||
width: 1.8em;
|
||||
width: 1.6em;
|
||||
}
|
||||
body .toolbar #globalScope.on {
|
||||
background-color: #000;
|
||||
|
|
|
@ -18,7 +18,6 @@ div > p:last-child {
|
|||
padding: 0;
|
||||
position: relative;
|
||||
vertical-align: top;
|
||||
white-space: normal;
|
||||
width: calc(50% - 2px);
|
||||
}
|
||||
#diff > .pane > div {
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
<body>
|
||||
|
||||
<p data-i18n="hostsFilesPrompt"></p>
|
||||
<p>
|
||||
<button id="buttonApply" class="custom important reloadAll disabled" data-i18n="hostsFilesApplyChanges"></button>
|
||||
<button id="buttonUpdate" class="custom important reloadAll disabled" data-i18n="hostsFilesUpdateNow"></button>
|
||||
|
@ -18,14 +17,28 @@
|
|||
</p>
|
||||
<ul id="options">
|
||||
<li><input type="checkbox" id="autoUpdate"><label data-i18n="hostsFilesAutoUpdatePrompt" for="autoUpdate"></label>
|
||||
<li><span id="listsOfBlockedHostsPrompt"></span>
|
||||
</ul>
|
||||
<ul id="lists">
|
||||
<div class="assets">
|
||||
<div data-i18n="assetsHostsSection"></div>
|
||||
<div>
|
||||
<p data-i18n="hostsFilesPrompt"></p>
|
||||
<p id="listsOfBlockedHostsPrompt"></p>
|
||||
<ul id="hosts">
|
||||
<li class="listEntry importURL"><input type="checkbox" id="importHosts"><label for="importHosts"data-i18n="assetsImportLabel"></label><!--
|
||||
--><textarea dir="ltr" spellcheck="false" placeholder="hostsFilesExternalListsHint"></textarea>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="externalLists">
|
||||
<p data-i18n="hostsFilesExternalListsHint" style="margin: 0 0 0.25em 0; font-size: 13px;"></p>
|
||||
<textarea id="externalHostsFiles" dir="ltr" spellcheck="false"></textarea>
|
||||
<div class="assets">
|
||||
<div data-i18n="assetsRecipesSection"></div>
|
||||
<div>
|
||||
<p data-i18n="assetsRecipesSummary"></p>
|
||||
<ul id="recipes">
|
||||
<li class="listEntry importURL"><input type="checkbox" id="importRecipes"><label for="importRecipes"data-i18n="assetsImportLabel"></label><!--
|
||||
--><textarea dir="ltr" spellcheck="false" placeholder="hostsFilesExternalListsHint"></textarea>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="templates" style="display: none;">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*******************************************************************************
|
||||
|
||||
µMatrix - a browser extension to black/white list requests.
|
||||
Copyright (C) 2013-2015 Raymond Hill
|
||||
uMatrix - a browser extension to black/white list requests.
|
||||
Copyright (C) 2013-2018 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
|
||||
|
@ -237,6 +237,11 @@ var registerAssetSource = function(assetKey, dict) {
|
|||
entry[prop] = dict[prop];
|
||||
}
|
||||
}
|
||||
// `content` property => `type` property
|
||||
if ( entry.type === undefined && entry.content !== undefined ) {
|
||||
entry.type = entry.content;
|
||||
entry.content = undefined;
|
||||
}
|
||||
var contentURL = dict.contentURL;
|
||||
if ( contentURL !== undefined ) {
|
||||
if ( typeof contentURL === 'string' ) {
|
||||
|
@ -337,6 +342,15 @@ var getAssetSourceRegistry = function(callback) {
|
|||
assetSourceRegistryStatus = [ callback ];
|
||||
|
||||
var registryReady = function() {
|
||||
// `content` property => `type` property
|
||||
for ( let key in assetSourceRegistry ) {
|
||||
let entry = assetSourceRegistry[key];
|
||||
if ( entry.type === undefined && entry.content !== undefined ) {
|
||||
entry.type = entry.content;
|
||||
entry.content = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
var callers = assetSourceRegistryStatus;
|
||||
assetSourceRegistryStatus = 'ready';
|
||||
var fn;
|
||||
|
@ -822,7 +836,15 @@ var updateNext = function() {
|
|||
if ( cacheEntry && (cacheEntry.writeTime + assetEntry.updateAfter * 86400000) > now ) {
|
||||
continue;
|
||||
}
|
||||
if ( fireNotification('before-asset-updated', { assetKey: assetKey }) ) {
|
||||
if (
|
||||
fireNotification(
|
||||
'before-asset-updated',
|
||||
{
|
||||
assetKey: assetKey,
|
||||
type: assetEntry.type
|
||||
}
|
||||
)
|
||||
) {
|
||||
return assetKey;
|
||||
}
|
||||
garbageCollectOne(assetKey);
|
||||
|
|
|
@ -177,14 +177,17 @@ return {
|
|||
deleteUnusedSessionCookiesAfter: 60,
|
||||
deleteLocalStorage: false,
|
||||
displayTextSize: '14px',
|
||||
externalHostsFiles: '',
|
||||
externalHostsFiles: [],
|
||||
externalRecipeFiles: [],
|
||||
iconBadgeEnabled: false,
|
||||
maxLoggedRequests: 1000,
|
||||
popupCollapseAllDomains: false,
|
||||
popupCollapseBlacklistedDomains: false,
|
||||
popupScopeLevel: 'domain',
|
||||
processHyperlinkAuditing: true,
|
||||
processReferer: false
|
||||
processReferer: false,
|
||||
selectedHostsFiles: [ '' ],
|
||||
selectedRecipeFiles: [ '' ]
|
||||
},
|
||||
|
||||
rawSettingsDefault: rawSettingsDefault,
|
||||
|
@ -202,8 +205,7 @@ return {
|
|||
pslAssetKey: 'public_suffix_list.dat',
|
||||
|
||||
// list of live hosts files
|
||||
liveHostsFiles: {
|
||||
},
|
||||
liveHostsFiles: new Map(),
|
||||
|
||||
// urls stats are kept on the back burner while waiting to be reactivated
|
||||
// in a tab or another.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uMatrix - a Chromium browser extension to black/white list requests.
|
||||
Copyright (C) 2014-2017 Raymond Hill
|
||||
Copyright (C) 2014-2018 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
|
||||
|
@ -32,7 +32,7 @@
|
|||
var listDetails = {},
|
||||
lastUpdateTemplateString = vAPI.i18n('hostsFilesLastUpdate'),
|
||||
hostsFilesSettingsHash,
|
||||
reValidExternalList = /[a-z-]+:\/\/\S*\/\S+/;
|
||||
reValidExternalList = /^[a-z-]+:\/\/\S*\/\S+$/m;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
@ -67,15 +67,15 @@ var renderHostsFiles = function(soft) {
|
|||
reExternalHostFile = /^https?:/;
|
||||
|
||||
// Assemble a pretty list name if possible
|
||||
var listNameFromListKey = function(listKey) {
|
||||
var list = listDetails.current[listKey] || listDetails.available[listKey];
|
||||
var listTitle = list ? list.title : '';
|
||||
var listNameFromListKey = function(collection, listKey) {
|
||||
let list = collection.get(listKey);
|
||||
let listTitle = list ? list.title : '';
|
||||
if ( listTitle === '' ) { return listKey; }
|
||||
return listTitle;
|
||||
};
|
||||
|
||||
var liFromListEntry = function(listKey, li) {
|
||||
var entry = listDetails.available[listKey],
|
||||
var liFromListEntry = function(collection, listKey, li) {
|
||||
var entry = collection.get(listKey),
|
||||
elem;
|
||||
if ( !li ) {
|
||||
li = listEntryTemplate.clone().nodeAt(0);
|
||||
|
@ -83,43 +83,31 @@ var renderHostsFiles = function(soft) {
|
|||
if ( li.getAttribute('data-listkey') !== listKey ) {
|
||||
li.setAttribute('data-listkey', listKey);
|
||||
elem = li.querySelector('input[type="checkbox"]');
|
||||
elem.checked = entry.off !== true;
|
||||
elem.checked = entry.selected === 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);
|
||||
elem.textContent = listNameFromListKey(collection, 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');
|
||||
if ( entry.supportURL ) {
|
||||
elem.setAttribute(
|
||||
'href',
|
||||
entry.supportURL ? entry.supportURL : ''
|
||||
);
|
||||
}
|
||||
li.classList.toggle('external', entry.external === true);
|
||||
}
|
||||
// https://github.com/gorhill/uBlock/issues/1429
|
||||
if ( !soft ) {
|
||||
elem = li.querySelector('input[type="checkbox"]');
|
||||
elem.checked = entry.off !== true;
|
||||
elem.checked = entry.selected === true;
|
||||
}
|
||||
elem = li.querySelector('span.counts');
|
||||
var text = '';
|
||||
if ( !isNaN(+entry.entryUsedCount) && !isNaN(+entry.entryCount) ) {
|
||||
text = listStatsTemplate
|
||||
.replace('{{used}}', renderNumber(entry.off ? 0 : entry.entryUsedCount))
|
||||
.replace('{{used}}', renderNumber(entry.selected ? entry.entryUsedCount : 0))
|
||||
.replace('{{total}}', renderNumber(entry.entryCount));
|
||||
}
|
||||
elem.textContent = text;
|
||||
|
@ -142,38 +130,56 @@ var renderHostsFiles = function(soft) {
|
|||
li.classList.remove('discard');
|
||||
return li;
|
||||
};
|
||||
|
||||
var 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');
|
||||
|
||||
var availableLists = details.available,
|
||||
listKeys = Object.keys(details.available);
|
||||
var onRenderAssetFiles = function(collection, listSelector) {
|
||||
var assetKeys = Array.from(collection.keys());
|
||||
|
||||
// Sort works this way:
|
||||
// - Send /^https?:/ items at the end (custom hosts file URL)
|
||||
listKeys.sort(function(a, b) {
|
||||
var ta = availableLists[a].title || a,
|
||||
tb = availableLists[b].title || b;
|
||||
assetKeys.sort(function(a, b) {
|
||||
var ta = collection.get(a).title || a,
|
||||
tb = collection.get(b).title || b;
|
||||
if ( reExternalHostFile.test(ta) === reExternalHostFile.test(tb) ) {
|
||||
return ta.localeCompare(tb);
|
||||
}
|
||||
return reExternalHostFile.test(tb) ? -1 : 1;
|
||||
});
|
||||
|
||||
var ulList = document.querySelector('#lists');
|
||||
for ( var i = 0; i < listKeys.length; i++ ) {
|
||||
var liEntry = liFromListEntry(listKeys[i], ulList.children[i]);
|
||||
let ulList = document.querySelector(listSelector),
|
||||
liImport = ulList.querySelector('.importURL');
|
||||
if ( liImport.parentNode !== null ) {
|
||||
liImport.parentNode.removeChild(liImport);
|
||||
}
|
||||
for ( let i = 0; i < assetKeys.length; i++ ) {
|
||||
let liEntry = liFromListEntry(
|
||||
collection,
|
||||
assetKeys[i],
|
||||
ulList.children[i]
|
||||
);
|
||||
if ( liEntry.parentElement === null ) {
|
||||
ulList.appendChild(liEntry);
|
||||
}
|
||||
}
|
||||
|
||||
uDom('#lists .listEntry.discard').remove();
|
||||
ulList.appendChild(liImport);
|
||||
};
|
||||
|
||||
var onAssetDataReceived = function(details) {
|
||||
// Preprocess.
|
||||
details.hosts = new Map(details.hosts);
|
||||
details.recipes = new Map(details.recipes);
|
||||
|
||||
// Before all, set context vars
|
||||
listDetails = details;
|
||||
|
||||
// Incremental rendering: this will allow us to easily discard unused
|
||||
// DOM list entries.
|
||||
uDom('#hosts .listEntry:not(.importURL)').addClass('discard');
|
||||
|
||||
onRenderAssetFiles(details.hosts, '#hosts');
|
||||
onRenderAssetFiles(details.recipes, '#recipes');
|
||||
|
||||
uDom('.listEntry.discard').remove();
|
||||
|
||||
uDom('#listsOfBlockedHostsPrompt').text(
|
||||
vAPI.i18n('hostsFilesStats').replace(
|
||||
'{{blockedHostnameCount}}',
|
||||
|
@ -188,21 +194,25 @@ var renderHostsFiles = function(soft) {
|
|||
renderWidgets();
|
||||
};
|
||||
|
||||
vAPI.messaging.send('hosts-files.js', { what: 'getLists' }, onListsReceived);
|
||||
vAPI.messaging.send(
|
||||
'hosts-files.js',
|
||||
{ what: 'getAssets' },
|
||||
onAssetDataReceived
|
||||
);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var renderWidgets = function() {
|
||||
uDom('#buttonUpdate').toggleClass('disabled', document.querySelector('body:not(.updating) #lists .listEntry.obsolete > input[type="checkbox"]:checked') === null);
|
||||
uDom('#buttonPurgeAll').toggleClass('disabled', document.querySelector('#lists .listEntry.cached') === null);
|
||||
uDom('#buttonUpdate').toggleClass('disabled', document.querySelector('body:not(.updating) .assets .listEntry.obsolete > input[type="checkbox"]:checked') === null);
|
||||
uDom('#buttonPurgeAll').toggleClass('disabled', document.querySelector('.assets .listEntry.cached') === null);
|
||||
uDom('#buttonApply').toggleClass('disabled', hostsFilesSettingsHash === hashFromCurrentFromSettings());
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var updateAssetStatus = function(details) {
|
||||
var li = document.querySelector('#lists .listEntry[data-listkey="' + details.key + '"]');
|
||||
var li = document.querySelector('.assets .listEntry[data-listkey="' + details.key + '"]');
|
||||
if ( li === null ) { return; }
|
||||
li.classList.toggle('failed', !!details.failed);
|
||||
li.classList.toggle('obsolete', !details.cached);
|
||||
|
@ -229,19 +239,21 @@ var updateAssetStatus = function(details) {
|
|||
var hashFromCurrentFromSettings = function() {
|
||||
var hash = [],
|
||||
listHash = [],
|
||||
listEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)'),
|
||||
liEntry,
|
||||
listEntries = document.querySelectorAll('.assets .listEntry[data-listkey]:not(.toRemove)'),
|
||||
i = listEntries.length;
|
||||
while ( i-- ) {
|
||||
liEntry = listEntries[i];
|
||||
let liEntry = listEntries[i];
|
||||
if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) {
|
||||
listHash.push(liEntry.getAttribute('data-listkey'));
|
||||
}
|
||||
}
|
||||
let textarea1 = document.querySelector('.assets .importURL > input[type="checkbox"]:checked ~ textarea');
|
||||
let textarea2 = document.querySelector('#recipes .importURL > input[type="checkbox"]:checked ~ textarea');
|
||||
hash.push(
|
||||
listHash.sort().join(),
|
||||
reValidExternalList.test(document.getElementById('externalHostsFiles').value),
|
||||
document.querySelector('#lists .listEntry.toRemove') !== null
|
||||
textarea1 !== null && reValidExternalList.test(textarea1.value),
|
||||
textarea2 !== null && reValidExternalList.test(textarea2.value),
|
||||
document.querySelector('.listEntry.toRemove') !== null
|
||||
);
|
||||
return hash.join();
|
||||
};
|
||||
|
@ -254,7 +266,7 @@ var onHostsFilesSettingsChanged = function() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
var onRemoveExternalHostsFile = function(ev) {
|
||||
var onRemoveExternalAsset = function(ev) {
|
||||
var liEntry = uDom(this).ancestors('[data-listkey]'),
|
||||
listKey = liEntry.attr('data-listkey');
|
||||
if ( listKey ) {
|
||||
|
@ -283,39 +295,50 @@ var onPurgeClicked = function() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
var selectHostsFiles = function(callback) {
|
||||
// Hosts files to select
|
||||
var toSelect = [],
|
||||
liEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)'),
|
||||
i = liEntries.length,
|
||||
liEntry;
|
||||
var selectAssets = function(callback) {
|
||||
var prepareChanges = function(listSelector) {
|
||||
var out = {
|
||||
toSelect: [],
|
||||
toImport: '',
|
||||
toRemove: []
|
||||
};
|
||||
|
||||
let root = document.querySelector(listSelector);
|
||||
|
||||
let liEntries = root.querySelectorAll('.listEntry[data-listkey]:not(.toRemove)'),
|
||||
i = liEntries.length;
|
||||
while ( i-- ) {
|
||||
liEntry = liEntries[i];
|
||||
let liEntry = liEntries[i];
|
||||
if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) {
|
||||
toSelect.push(liEntry.getAttribute('data-listkey'));
|
||||
out.toSelect.push(liEntry.getAttribute('data-listkey'));
|
||||
}
|
||||
}
|
||||
|
||||
// External hosts files to remove
|
||||
var toRemove = [];
|
||||
liEntries = document.querySelectorAll('#lists .listEntry.toRemove[data-listkey]');
|
||||
liEntries = root.querySelectorAll('.listEntry.toRemove[data-listkey]');
|
||||
i = liEntries.length;
|
||||
while ( i-- ) {
|
||||
toRemove.push(liEntries[i].getAttribute('data-listkey'));
|
||||
out.toRemove.push(liEntries[i].getAttribute('data-listkey'));
|
||||
}
|
||||
|
||||
// External hosts files to import
|
||||
var externalListsElem = document.getElementById('externalHostsFiles'),
|
||||
toImport = externalListsElem.value.trim();
|
||||
externalListsElem.value = '';
|
||||
let input = root.querySelector('.importURL > input[type="checkbox"]:checked');
|
||||
if ( input !== null ) {
|
||||
let textarea = root.querySelector('.importURL textarea');
|
||||
out.toImport = textarea.value.trim();
|
||||
textarea.value = '';
|
||||
input.checked = false;
|
||||
}
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
vAPI.messaging.send(
|
||||
'hosts-files.js',
|
||||
{
|
||||
what: 'selectHostsFiles',
|
||||
toSelect: toSelect,
|
||||
toImport: toImport,
|
||||
toRemove: toRemove
|
||||
what: 'selectAssets',
|
||||
hosts: prepareChanges('#hosts'),
|
||||
recipes: prepareChanges('#recipes')
|
||||
},
|
||||
callback
|
||||
);
|
||||
|
@ -327,8 +350,13 @@ var selectHostsFiles = function(callback) {
|
|||
|
||||
var buttonApplyHandler = function() {
|
||||
uDom('#buttonApply').removeClass('enabled');
|
||||
selectHostsFiles(function() {
|
||||
selectAssets(function(response) {
|
||||
if ( response && response.hostsChanged ) {
|
||||
vAPI.messaging.send('hosts-files.js', { what: 'reloadHostsFiles' });
|
||||
}
|
||||
if ( response && response.recipesChanged ) {
|
||||
vAPI.messaging.send('hosts-files.js', { what: 'reloadRecipeFiles' });
|
||||
}
|
||||
});
|
||||
renderWidgets();
|
||||
};
|
||||
|
@ -337,7 +365,7 @@ var buttonApplyHandler = function() {
|
|||
|
||||
var buttonUpdateHandler = function() {
|
||||
uDom('#buttonUpdate').removeClass('enabled');
|
||||
selectHostsFiles(function() {
|
||||
selectAssets(function() {
|
||||
document.body.classList.add('updating');
|
||||
vAPI.messaging.send('hosts-files.js', { what: 'forceUpdateAssets' });
|
||||
renderWidgets();
|
||||
|
@ -377,10 +405,10 @@ 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);
|
||||
uDom('.assets').on('change', '.listEntry > input', onHostsFilesSettingsChanged);
|
||||
uDom('.assets').on('input', '.listEntry.importURL textarea', onHostsFilesSettingsChanged);
|
||||
uDom('.assets').on('click', '.listEntry > a.remove', onRemoveExternalAsset);
|
||||
uDom('.assets').on('click', 'span.cache', onPurgeClicked);
|
||||
|
||||
renderHostsFiles();
|
||||
|
||||
|
|
|
@ -41,8 +41,8 @@ function onMessage(request, sender, callback) {
|
|||
µm.assets.get(request.url, { dontCache: true }, callback);
|
||||
return;
|
||||
|
||||
case 'selectHostsFiles':
|
||||
µm.selectHostsFiles(request, callback);
|
||||
case 'selectAssets':
|
||||
µm.selectAssets(request, callback);
|
||||
return;
|
||||
|
||||
default:
|
||||
|
@ -319,6 +319,14 @@ var matrixSnapshotFromTabId = function(details, callback) {
|
|||
var onMessage = function(request, sender, callback) {
|
||||
// Async
|
||||
switch ( request.what ) {
|
||||
case 'fetchRecipes':
|
||||
µm.recipeManager.fetch(
|
||||
request.srcHostname,
|
||||
request.desHostnames,
|
||||
callback
|
||||
);
|
||||
return;
|
||||
|
||||
case 'matrixSnapshot':
|
||||
matrixSnapshotFromTabId(request, callback);
|
||||
return;
|
||||
|
@ -331,6 +339,14 @@ var onMessage = function(request, sender, callback) {
|
|||
var response;
|
||||
|
||||
switch ( request.what ) {
|
||||
case 'applyRecipe':
|
||||
µm.recipeManager.apply(request);
|
||||
break;
|
||||
|
||||
case 'fetchRecipeCommitStatuses':
|
||||
response = µm.recipeManager.commitStatuses(request);
|
||||
break;
|
||||
|
||||
case 'toggleMatrixSwitch':
|
||||
µm.tMatrix.setSwitchZ(
|
||||
request.switchName,
|
||||
|
@ -713,41 +729,26 @@ 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 getAssets = function(callback) {
|
||||
var r = {
|
||||
autoUpdate: µm.userSettings.autoUpdate,
|
||||
available: null,
|
||||
cache: null,
|
||||
current: µm.liveHostsFiles,
|
||||
blockedHostnameCount: µm.ubiquitousBlacklist.count
|
||||
blockedHostnameCount: µm.ubiquitousBlacklist.count,
|
||||
hosts: null,
|
||||
recipes: null,
|
||||
cache: null
|
||||
};
|
||||
var onMetadataReady = function(entries) {
|
||||
r.cache = entries;
|
||||
prepEntries(r.cache);
|
||||
callback(r);
|
||||
};
|
||||
var onAvailableHostsFilesReady = function(lists) {
|
||||
r.available = lists;
|
||||
prepEntries(r.available);
|
||||
var onAvailableRecipeFilesReady = function(collection) {
|
||||
r.recipes = Array.from(collection);
|
||||
µm.assets.metadata(onMetadataReady);
|
||||
};
|
||||
var onAvailableHostsFilesReady = function(collection) {
|
||||
r.hosts = Array.from(collection);
|
||||
µm.getAvailableRecipeFiles(onAvailableRecipeFilesReady);
|
||||
};
|
||||
µm.getAvailableHostsFiles(onAvailableHostsFilesReady);
|
||||
};
|
||||
|
||||
|
@ -758,8 +759,8 @@ var onMessage = function(request, sender, callback) {
|
|||
|
||||
// Async
|
||||
switch ( request.what ) {
|
||||
case 'getLists':
|
||||
return getLists(callback);
|
||||
case 'getAssets':
|
||||
return getAssets(callback);
|
||||
|
||||
default:
|
||||
break;
|
||||
|
@ -805,7 +806,7 @@ var µm = µMatrix;
|
|||
/******************************************************************************/
|
||||
|
||||
var restoreUserData = function(userData) {
|
||||
var countdown = 4;
|
||||
var countdown = 0;
|
||||
var onCountdown = function() {
|
||||
countdown -= 1;
|
||||
if ( countdown === 0 ) {
|
||||
|
@ -814,11 +815,18 @@ var restoreUserData = function(userData) {
|
|||
};
|
||||
|
||||
var onAllRemoved = function() {
|
||||
let µm = µMatrix;
|
||||
countdown += 1;
|
||||
vAPI.storage.set(userData.settings, onCountdown);
|
||||
vAPI.storage.set({ userMatrix: userData.rules }, onCountdown);
|
||||
vAPI.storage.set({ liveHostsFiles: userData.hostsFiles }, onCountdown);
|
||||
countdown += 1;
|
||||
let bin = { userMatrix: userData.rules };
|
||||
if ( userData.hostsFiles instanceof Object ) {
|
||||
bin.liveHostsFiles = userData.hostsFiles;
|
||||
}
|
||||
vAPI.storage.set(bin, onCountdown);
|
||||
if ( userData.rawSettings instanceof Object ) {
|
||||
µMatrix.saveRawSettings(userData.rawSettings, onCountdown);
|
||||
countdown += 1;
|
||||
µm.saveRawSettings(userData.rawSettings, onCountdown);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -856,7 +864,6 @@ var onMessage = function(request, sender, callback) {
|
|||
when: Date.now(),
|
||||
settings: µm.userSettings,
|
||||
rules: µm.pMatrix.toString(),
|
||||
hostsFiles: µm.liveHostsFiles,
|
||||
rawSettings: µm.rawSettings
|
||||
};
|
||||
break;
|
||||
|
|
143
src/js/popup.js
143
src/js/popup.js
|
@ -1058,6 +1058,7 @@ var makeMenu = function() {
|
|||
initScopeCell();
|
||||
updateMatrixButtons();
|
||||
resizePopup();
|
||||
recipeManager.fetch();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -1279,16 +1280,6 @@ function updateMatrixButtons() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
function revertAll() {
|
||||
var request = {
|
||||
what: 'revertTemporaryMatrix'
|
||||
};
|
||||
vAPI.messaging.send('popup.js', request, updateMatrixSnapshot);
|
||||
dropDownMenuHide();
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function buttonReloadHandler(ev) {
|
||||
vAPI.messaging.send('popup.js', {
|
||||
what: 'forceReloadTab',
|
||||
|
@ -1299,6 +1290,126 @@ function buttonReloadHandler(ev) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
let recipeManager = (function() {
|
||||
let recipes = [];
|
||||
let reScopeAlias = /(^|\s+)_(\s+|$)/g;
|
||||
|
||||
function createEntry(name, ruleset, parent) {
|
||||
let li = document.querySelector('#templates li.recipe')
|
||||
.cloneNode(true);
|
||||
li.querySelector('.name').textContent = name;
|
||||
li.querySelector('.ruleset').textContent = ruleset;
|
||||
if ( parent ) {
|
||||
parent.appendChild(li);
|
||||
}
|
||||
return li;
|
||||
}
|
||||
|
||||
function apply(ev) {
|
||||
if ( ev.target.classList.contains('expander') ) {
|
||||
ev.currentTarget.classList.toggle('expanded');
|
||||
return;
|
||||
}
|
||||
let root = ev.currentTarget;
|
||||
let ruleset = root.querySelector('.ruleset');
|
||||
let commit = ev.target.classList.contains('committer');
|
||||
vAPI.messaging.send(
|
||||
'popup.js',
|
||||
{
|
||||
what: 'applyRecipe',
|
||||
ruleset: ruleset.textContent,
|
||||
commit: commit
|
||||
},
|
||||
updateMatrixSnapshot
|
||||
);
|
||||
if ( commit ) {
|
||||
root.classList.remove('mustCommit');
|
||||
}
|
||||
//dropDownMenuHide();
|
||||
}
|
||||
|
||||
function show(details) {
|
||||
let root = document.querySelector('#dropDownMenuRecipes .dropdown-menu');
|
||||
let ul = document.createElement('ul');
|
||||
for ( let recipe of details.recipes ) {
|
||||
let li = createEntry(
|
||||
recipe.name,
|
||||
recipe.ruleset.replace(reScopeAlias, '$1' + details.scope + '$2'),
|
||||
ul
|
||||
);
|
||||
li.classList.toggle('mustCommit', recipe.mustCommit === true);
|
||||
li.addEventListener('click', apply);
|
||||
}
|
||||
root.replaceChild(ul, root.querySelector('ul'));
|
||||
dropDownMenuShow(uDom.nodeFromId('buttonRecipes'));
|
||||
}
|
||||
|
||||
function beforeShow() {
|
||||
if ( recipes.length === 0 ) { return; }
|
||||
vAPI.messaging.send(
|
||||
'popup.js',
|
||||
{
|
||||
what: 'fetchRecipeCommitStatuses',
|
||||
scope: matrixSnapshot.scope,
|
||||
recipes: recipes
|
||||
},
|
||||
show
|
||||
);
|
||||
}
|
||||
|
||||
function fetch() {
|
||||
let onResponse = function(response) {
|
||||
recipes = Array.isArray(response) ? response : [];
|
||||
let button = uDom.nodeFromId('buttonRecipes');
|
||||
if ( recipes.length === 0 ) {
|
||||
button.classList.add('disabled');
|
||||
return;
|
||||
}
|
||||
button.classList.remove('disabled');
|
||||
button.querySelector('span.badge').textContent = recipes.length;
|
||||
};
|
||||
|
||||
let desHostnames = [];
|
||||
for ( let hostname in matrixSnapshot.rows ) {
|
||||
if ( matrixSnapshot.rows.hasOwnProperty(hostname) === false ) {
|
||||
continue;
|
||||
}
|
||||
let row = matrixSnapshot.rows[hostname];
|
||||
if ( row.domain === matrixSnapshot.domain ) { continue; }
|
||||
if ( row.counts[0] !== 0 || row.domain === hostname ) {
|
||||
desHostnames.push(hostname);
|
||||
}
|
||||
}
|
||||
|
||||
vAPI.messaging.send('popup.js',
|
||||
{
|
||||
what: 'fetchRecipes',
|
||||
srcHostname: matrixSnapshot.hostname,
|
||||
desHostnames: desHostnames
|
||||
},
|
||||
onResponse
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
fetch: fetch,
|
||||
show: beforeShow,
|
||||
apply: apply
|
||||
};
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function revertAll() {
|
||||
vAPI.messaging.send(
|
||||
'popup.js',
|
||||
{ what: 'revertTemporaryMatrix' },
|
||||
updateMatrixSnapshot
|
||||
);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
function mouseenterMatrixCellHandler(ev) {
|
||||
matrixCellHotspots.appendTo(ev.target);
|
||||
}
|
||||
|
@ -1324,8 +1435,7 @@ function gotoExtensionURL(ev) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
function dropDownMenuShow(ev) {
|
||||
var button = ev.target;
|
||||
function dropDownMenuShow(button) {
|
||||
var menuOverlay = document.getElementById(button.getAttribute('data-dropdown-menu'));
|
||||
var butnRect = button.getBoundingClientRect();
|
||||
var viewRect = document.body.getBoundingClientRect();
|
||||
|
@ -1512,15 +1622,20 @@ uDom('body')
|
|||
.on('mouseleave', '.matCell', mouseleaveMatrixCellHandler);
|
||||
uDom('#specificScope').on('click', selectSpecificScope);
|
||||
uDom('#globalScope').on('click', selectGlobalScope);
|
||||
uDom('#buttonMtxSwitches').on('click', function(ev) {
|
||||
dropDownMenuShow(ev.target);
|
||||
});
|
||||
uDom('[id^="mtxSwitch_"]').on('click', toggleMatrixSwitch);
|
||||
uDom('#buttonPersist').on('click', persistMatrix);
|
||||
uDom('#buttonRevertScope').on('click', revertMatrix);
|
||||
|
||||
uDom('#buttonRecipes').on('click', function() {
|
||||
recipeManager.show();
|
||||
});
|
||||
|
||||
uDom('#buttonRevertAll').on('click', revertAll);
|
||||
uDom('#buttonReload').on('click', buttonReloadHandler);
|
||||
uDom('.extensionURL').on('click', gotoExtensionURL);
|
||||
|
||||
uDom('body').on('click', '[data-dropdown-menu]', dropDownMenuShow);
|
||||
uDom('body').on('click', '.dropdown-menu-capture', dropDownMenuHide);
|
||||
|
||||
uDom('#matList').on('click', '.g4Meta', function(ev) {
|
||||
|
|
217
src/js/recipe-manager.js
Normal file
217
src/js/recipe-manager.js
Normal file
|
@ -0,0 +1,217 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uMatrix - a Chromium browser extension to black/white list requests.
|
||||
Copyright (C) 2018 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
|
||||
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://github.com/gorhill/uMatrix
|
||||
*/
|
||||
|
||||
/* global punycode */
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
µMatrix.recipeManager = (function() {
|
||||
let rawRecipes = [];
|
||||
let recipeIdGenerator = 1;
|
||||
let recipeBook = new Map();
|
||||
let reValidRecipeFile = /^! uMatrix: Ruleset recipes [0-9.]+\n/;
|
||||
let reNoUnicode = /^[\x00-\x7F]$/;
|
||||
|
||||
var authorFromHeader = function(header) {
|
||||
let match = /^! +maintainer: +([^\n]+)/im.exec(header);
|
||||
return match !== null ? match[1].trim() : '';
|
||||
};
|
||||
|
||||
var conditionMatch = function(condition, srcHostname, desHostnames) {
|
||||
let i = condition.indexOf(' ');
|
||||
if ( i === -1 ) { return false; }
|
||||
let hn = condition.slice(0, i).trim();
|
||||
if ( hn !== '*' && srcHostname.endsWith(hn) === false ) {
|
||||
return false;
|
||||
}
|
||||
hn = condition.slice(i + 1).trim();
|
||||
if ( hn === '*' ) { return true; }
|
||||
for ( let desHostname of desHostnames ) {
|
||||
if ( desHostname.endsWith(hn) ) { return true; }
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var toASCII = function(rule) {
|
||||
if ( reNoUnicode.test(rule) ) { return rule; }
|
||||
let parts = rule.split(/\s+/);
|
||||
for ( let i = 0; i < parts.length; i++ ) {
|
||||
parts[i] = punycode.toASCII(parts[i]);
|
||||
}
|
||||
return parts.join(' ');
|
||||
};
|
||||
|
||||
var compareLength = function(a, b) {
|
||||
return b.length - a.length;
|
||||
};
|
||||
|
||||
var getTokens = function(s) {
|
||||
let tokens = s.match(/[a-z0-9]+/gi);
|
||||
if ( tokens === null ) { return []; }
|
||||
return tokens;
|
||||
};
|
||||
|
||||
var addRecipe = function(recipe) {
|
||||
let tokens = getTokens(recipe.condition);
|
||||
tokens.sort(compareLength);
|
||||
let token = tokens[0];
|
||||
let recipes = recipeBook.get(token);
|
||||
if ( recipes === undefined ) {
|
||||
recipeBook.set(token, recipes = []);
|
||||
}
|
||||
recipes.push(recipe);
|
||||
};
|
||||
|
||||
var fromString = function(raw) {
|
||||
var recipeName,
|
||||
recipeCondition,
|
||||
recipeRuleset;
|
||||
let rawHeader = raw.slice(0, 1024);
|
||||
if ( reValidRecipeFile.test(rawHeader) === false ) { return; }
|
||||
let maintainer = authorFromHeader(rawHeader);
|
||||
let lineIter = new µMatrix.LineIterator(raw);
|
||||
for (;;) {
|
||||
let line = lineIter.next().trim();
|
||||
if ( line.length === 0 ) {
|
||||
if (
|
||||
recipeName !== undefined &&
|
||||
recipeCondition !== undefined &&
|
||||
recipeRuleset.length !== 0
|
||||
) {
|
||||
addRecipe({
|
||||
id: recipeIdGenerator++,
|
||||
name: recipeName,
|
||||
maintainer: maintainer,
|
||||
condition: recipeCondition,
|
||||
ruleset: recipeRuleset
|
||||
});
|
||||
}
|
||||
recipeName = undefined;
|
||||
}
|
||||
if ( lineIter.eot() && recipeName === undefined ) { break; }
|
||||
if ( line.length === 0 ) { continue; }
|
||||
let c = line.charCodeAt(0);
|
||||
if ( c === 0x23 /* '#' */ || c === 0x21 /* '!' */ ) { continue; }
|
||||
if ( recipeName === undefined ) {
|
||||
recipeName = line;
|
||||
recipeCondition = undefined;
|
||||
continue;
|
||||
}
|
||||
if ( recipeCondition === undefined ) {
|
||||
recipeCondition = toASCII(line);
|
||||
recipeRuleset = '';
|
||||
continue;
|
||||
}
|
||||
if ( recipeRuleset.length !== 0 ) {
|
||||
recipeRuleset += '\n';
|
||||
}
|
||||
recipeRuleset += toASCII(line);
|
||||
}
|
||||
};
|
||||
|
||||
var fromPendingStrings = function() {
|
||||
if ( rawRecipes.length === 0 ) { return; }
|
||||
for ( var raw of rawRecipes ) {
|
||||
fromString(raw);
|
||||
}
|
||||
rawRecipes = [];
|
||||
};
|
||||
|
||||
return {
|
||||
apply: function(details) {
|
||||
let µm = µMatrix;
|
||||
let tMatrix = µm.tMatrix;
|
||||
let pMatrix = µm.pMatrix;
|
||||
let mustPersist = false;
|
||||
for ( let rule of details.ruleset.split('\n') ) {
|
||||
let parts = rule.split(/\s+/);
|
||||
let action = tMatrix.evaluateCellZXY(parts[0], parts[1], parts[2]);
|
||||
if ( action === 1 ) {
|
||||
tMatrix.whitelistCell(parts[0], parts[1], parts[2]);
|
||||
}
|
||||
if ( details.commit !== true ) { continue; }
|
||||
action = pMatrix.evaluateCellZXY(parts[0], parts[1], parts[2]);
|
||||
if ( action === 1 ) {
|
||||
pMatrix.whitelistCell(parts[0], parts[1], parts[2]);
|
||||
mustPersist = true;
|
||||
}
|
||||
}
|
||||
if ( mustPersist ) {
|
||||
µm.saveMatrix();
|
||||
}
|
||||
},
|
||||
fetch: function(srcHostname, desHostnames, callback) {
|
||||
fromPendingStrings();
|
||||
let out = [];
|
||||
let fetched = new Set();
|
||||
let tokens = getTokens(srcHostname + ' ' + desHostnames.join(' '));
|
||||
for ( let token of tokens ) {
|
||||
let recipes = recipeBook.get(token);
|
||||
if ( recipes === undefined ) { continue; }
|
||||
for ( let recipe of recipes ) {
|
||||
if ( fetched.has(recipe.id) ) { continue; }
|
||||
if (
|
||||
conditionMatch(
|
||||
recipe.condition,
|
||||
srcHostname,
|
||||
desHostnames
|
||||
)
|
||||
) {
|
||||
out.push(recipe);
|
||||
fetched.add(recipe.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
callback(out);
|
||||
},
|
||||
commitStatuses: function(details) {
|
||||
let matrix = µMatrix.pMatrix;
|
||||
for ( let recipe of details.recipes ) {
|
||||
let ruleIter = new µMatrix.LineIterator(recipe.ruleset);
|
||||
while ( ruleIter.eot() === false ) {
|
||||
let parts = ruleIter.next().split(/\s+/);
|
||||
if (
|
||||
matrix.evaluateCellZXY(
|
||||
details.scope,
|
||||
parts[1],
|
||||
parts[2]
|
||||
) === 1
|
||||
) {
|
||||
recipe.mustCommit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return details;
|
||||
},
|
||||
fromString: function(raw) {
|
||||
rawRecipes.push(raw);
|
||||
},
|
||||
reset: function() {
|
||||
rawRecipes.length = 0;
|
||||
recipeBook.clear();
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
|
@ -76,6 +76,7 @@ var onTabsReady = function(tabs) {
|
|||
|
||||
var onUserSettingsLoaded = function() {
|
||||
µm.loadHostsFiles();
|
||||
µm.loadRecipes();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
@ -54,14 +54,94 @@
|
|||
callback = this.noopFunc;
|
||||
}
|
||||
|
||||
var settingsLoaded = function(store) {
|
||||
// console.log('storage.js > loaded user settings');
|
||||
|
||||
µm.userSettings = store;
|
||||
|
||||
var onAvailableRulesetFilesReady = function(availableRulesetFiles) {
|
||||
let selectedAssetKeys = new Set();
|
||||
for ( let entry of availableRulesetFiles ) {
|
||||
let assetKey = entry[0];
|
||||
let assetLang = entry[1].lang;
|
||||
if ( assetLang === undefined ) {
|
||||
selectedAssetKeys.add(assetKey);
|
||||
continue;
|
||||
}
|
||||
for ( let lang of navigator.languages ) {
|
||||
if ( assetLang.indexOf(lang) !== -1 ) {
|
||||
selectedAssetKeys.add(assetKey);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
µm.userSettings.selectedRecipeFiles = Array.from(selectedAssetKeys);
|
||||
vAPI.storage.set({
|
||||
selectedRecipeFiles: µm.userSettings.selectedRecipeFiles
|
||||
});
|
||||
callback(µm.userSettings);
|
||||
};
|
||||
|
||||
var initializeSelectedRulesetFiles = function() {
|
||||
if (
|
||||
µm.userSettings.selectedRecipeFiles.length === 1 &&
|
||||
µm.userSettings.selectedRecipeFiles[0] === ''
|
||||
) {
|
||||
µm.getAvailableRecipeFiles(onAvailableRulesetFilesReady);
|
||||
return;
|
||||
}
|
||||
callback(µm.userSettings);
|
||||
};
|
||||
|
||||
var onAvailableHostsFilesReady = function(availableHostFiles) {
|
||||
µm.userSettings.selectedHostsFiles =
|
||||
Array.from(availableHostFiles.keys());
|
||||
vAPI.storage.set({
|
||||
selectedHostsFiles: µm.userSettings.selectedHostsFiles
|
||||
});
|
||||
initializeSelectedRulesetFiles();
|
||||
};
|
||||
|
||||
var migrateSelectedHostsFiles = function(bin) {
|
||||
if (
|
||||
bin instanceof Object === false ||
|
||||
bin.liveHostsFiles instanceof Object === false
|
||||
) {
|
||||
µm.getAvailableHostsFiles(onAvailableHostsFilesReady);
|
||||
return;
|
||||
}
|
||||
let selectedHostsFiles = new Set();
|
||||
for ( let entry of µm.toMap(bin.liveHostsFiles) ) {
|
||||
if ( entry[1].off !== true ) {
|
||||
selectedHostsFiles.add(entry[0]);
|
||||
}
|
||||
}
|
||||
µm.userSettings.selectedHostsFiles = Array.from(selectedHostsFiles);
|
||||
vAPI.storage.set({
|
||||
selectedHostsFiles: µm.userSettings.selectedHostsFiles
|
||||
});
|
||||
initializeSelectedRulesetFiles();
|
||||
};
|
||||
|
||||
var initializeSelectedHostsFiles = function() {
|
||||
// Backward-compatibility: populate the new list selection array with
|
||||
// existing data.
|
||||
if (
|
||||
µm.userSettings.selectedHostsFiles.length === 1 &&
|
||||
µm.userSettings.selectedHostsFiles[0] === ''
|
||||
) {
|
||||
vAPI.storage.get('liveHostsFiles', migrateSelectedHostsFiles);
|
||||
return;
|
||||
}
|
||||
initializeSelectedRulesetFiles();
|
||||
};
|
||||
|
||||
var settingsLoaded = function(store) {
|
||||
µm.userSettings = store;
|
||||
if ( typeof µm.userSettings.externalHostsFiles === 'string' ) {
|
||||
µm.userSettings.externalHostsFiles =
|
||||
µm.userSettings.externalHostsFiles.length !== 0 ?
|
||||
µm.userSettings.externalHostsFiles.split('\n') :
|
||||
[];
|
||||
}
|
||||
initializeSelectedHostsFiles();
|
||||
};
|
||||
|
||||
vAPI.storage.get(this.userSettings, settingsLoaded);
|
||||
};
|
||||
|
||||
|
@ -161,13 +241,10 @@
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
// save white/blacklist
|
||||
µMatrix.saveMatrix = function() {
|
||||
µMatrix.XAL.keyvalSetOne('userMatrix', this.pMatrix.toString());
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
µMatrix.loadMatrix = function(callback) {
|
||||
if ( typeof callback !== 'function' ) {
|
||||
callback = this.noopFunc;
|
||||
|
@ -185,72 +262,95 @@
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
µMatrix.listKeysFromCustomHostsFiles = function(raw) {
|
||||
µMatrix.loadRecipes = function(reset, callback) {
|
||||
if ( typeof callback !== 'function' ) {
|
||||
callback = this.noopFunc;
|
||||
}
|
||||
|
||||
let µm = this,
|
||||
countdownCount = µm.userSettings.selectedRecipeFiles.length;
|
||||
|
||||
if ( reset ) {
|
||||
µm.recipeManager.reset();
|
||||
}
|
||||
|
||||
var onLoaded = function(details) {
|
||||
if ( details.content ) {
|
||||
µm.recipeManager.fromString(details.content);
|
||||
}
|
||||
countdownCount -= 1;
|
||||
if ( countdownCount === 0 ) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
for ( let assetKey of µm.userSettings.selectedRecipeFiles ) {
|
||||
this.assets.get(assetKey, onLoaded);
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
µMatrix.assetKeysFromImportedAssets = function(raw) {
|
||||
var out = new Set(),
|
||||
reIgnore = /^[!#]/,
|
||||
reValid = /^[a-z-]+:\/\/\S+/,
|
||||
lineIter = new this.LineIterator(raw),
|
||||
location;
|
||||
reValid = /^[a-z-]+:\/\/\S+\/./,
|
||||
lineIter = new this.LineIterator(raw);
|
||||
while ( lineIter.eot() === false ) {
|
||||
location = lineIter.next().trim();
|
||||
let location = lineIter.next().trim();
|
||||
if ( reIgnore.test(location) || !reValid.test(location) ) { continue; }
|
||||
out.add(location);
|
||||
}
|
||||
return this.setToArray(out);
|
||||
return out;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
µMatrix.getAvailableHostsFiles = function(callback) {
|
||||
var µm = this,
|
||||
availableHostsFiles = {};
|
||||
availableHostsFiles = new Map();
|
||||
|
||||
// Custom filter lists.
|
||||
var importedListKeys = this.listKeysFromCustomHostsFiles(µm.userSettings.externalHostsFiles),
|
||||
i = importedListKeys.length,
|
||||
listKey, entry;
|
||||
while ( i-- ) {
|
||||
listKey = importedListKeys[i];
|
||||
entry = {
|
||||
content: 'filters',
|
||||
contentURL: listKey,
|
||||
var importedListKeys = new Set(µm.userSettings.externalHostsFiles);
|
||||
|
||||
for ( let assetKey of importedListKeys ) {
|
||||
let entry = {
|
||||
type: 'filters',
|
||||
contentURL: assetKey,
|
||||
external: true,
|
||||
submitter: 'user',
|
||||
title: listKey
|
||||
title: assetKey
|
||||
};
|
||||
availableHostsFiles[listKey] = entry;
|
||||
this.assets.registerAssetSource(listKey, entry);
|
||||
this.assets.registerAssetSource(assetKey, entry);
|
||||
availableHostsFiles.set(assetKey, entry);
|
||||
}
|
||||
|
||||
// selected lists
|
||||
var onSelectedHostsFilesLoaded = function(bin) {
|
||||
// Now get user's selection of lists
|
||||
for ( var assetKey in bin.liveHostsFiles ) {
|
||||
var availableEntry = availableHostsFiles[assetKey];
|
||||
if ( availableEntry === undefined ) { continue; }
|
||||
var liveEntry = bin.liveHostsFiles[assetKey];
|
||||
availableEntry.off = liveEntry.off || false;
|
||||
if ( liveEntry.entryCount !== undefined ) {
|
||||
availableEntry.entryCount = liveEntry.entryCount;
|
||||
// Populate available lists with useful data.
|
||||
var onHostsFilesDataReady = function(bin) {
|
||||
if ( bin && bin.liveHostsFiles ) {
|
||||
for ( let entry of µm.toMap(bin.liveHostsFiles) ) {
|
||||
let assetKey = entry[0];
|
||||
let availableAsset = availableHostsFiles.get(assetKey);
|
||||
if ( availableAsset === undefined ) { continue; }
|
||||
let liveAsset = entry[1];
|
||||
if ( liveAsset.entryCount !== undefined ) {
|
||||
availableAsset.entryCount = liveAsset.entryCount;
|
||||
}
|
||||
if ( liveEntry.entryUsedCount !== undefined ) {
|
||||
availableEntry.entryUsedCount = liveEntry.entryUsedCount;
|
||||
if ( liveAsset.entryUsedCount !== undefined ) {
|
||||
availableAsset.entryUsedCount = liveAsset.entryUsedCount;
|
||||
}
|
||||
// This may happen if the list name was pulled from the list content
|
||||
if ( availableEntry.title === '' && liveEntry.title !== undefined ) {
|
||||
availableEntry.title = liveEntry.title;
|
||||
if ( availableAsset.title === '' && liveAsset.title !== undefined ) {
|
||||
availableAsset.title = liveAsset.title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unreferenced imported filter lists.
|
||||
var dict = new Set(importedListKeys);
|
||||
for ( assetKey in availableHostsFiles ) {
|
||||
var entry = availableHostsFiles[assetKey];
|
||||
if ( entry.submitter !== 'user' ) { continue; }
|
||||
if ( dict.has(assetKey) ) { continue; }
|
||||
delete availableHostsFiles[assetKey];
|
||||
µm.assets.unregisterAssetSource(assetKey);
|
||||
µm.assets.remove(assetKey);
|
||||
for ( let asseyKey of µm.userSettings.selectedHostsFiles ) {
|
||||
let asset = availableHostsFiles.get(asseyKey);
|
||||
if ( asset !== undefined ) {
|
||||
asset.selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
callback(availableHostsFiles);
|
||||
|
@ -258,18 +358,22 @@
|
|||
|
||||
// built-in lists
|
||||
var onBuiltinHostsFilesLoaded = function(entries) {
|
||||
for ( var assetKey in entries ) {
|
||||
for ( let assetKey in entries ) {
|
||||
if ( entries.hasOwnProperty(assetKey) === false ) { continue; }
|
||||
entry = entries[assetKey];
|
||||
if ( entry.content !== 'filters' ) { continue; }
|
||||
availableHostsFiles[assetKey] = objectAssign({}, entry);
|
||||
let entry = entries[assetKey];
|
||||
if ( entry.type !== 'filters' ) { continue; }
|
||||
if (
|
||||
entry.submitter === 'user' &&
|
||||
importedListKeys.has(assetKey) === false
|
||||
) {
|
||||
µm.assets.unregisterAssetSource(assetKey);
|
||||
µm.assets.remove(assetKey);
|
||||
continue;
|
||||
}
|
||||
availableHostsFiles.set(assetKey, objectAssign({}, entry));
|
||||
}
|
||||
|
||||
// Now get user's selection of lists
|
||||
vAPI.storage.get(
|
||||
{ 'liveHostsFiles': availableHostsFiles },
|
||||
onSelectedHostsFilesLoaded
|
||||
);
|
||||
vAPI.storage.get('liveHostsFiles', onHostsFilesDataReady);
|
||||
};
|
||||
|
||||
this.assets.metadata(onBuiltinHostsFilesLoaded);
|
||||
|
@ -277,6 +381,56 @@
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
µMatrix.getAvailableRecipeFiles = function(callback) {
|
||||
var µm = this,
|
||||
availableRecipeFiles = new Map();
|
||||
|
||||
// Imported recipe resources.
|
||||
var importedResourceKeys = new Set(µm.userSettings.externalRecipeFiles);
|
||||
|
||||
for ( let assetKey of importedResourceKeys ) {
|
||||
let entry = {
|
||||
type: 'recipes',
|
||||
contentURL: assetKey,
|
||||
external: true,
|
||||
submitter: 'user',
|
||||
title: assetKey
|
||||
};
|
||||
this.assets.registerAssetSource(assetKey, entry);
|
||||
availableRecipeFiles.set(assetKey, entry);
|
||||
}
|
||||
|
||||
var onBuiltinRecipeFilesLoaded = function(entries) {
|
||||
for ( let assetKey in entries ) {
|
||||
if ( entries.hasOwnProperty(assetKey) === false ) { continue; }
|
||||
let entry = entries[assetKey];
|
||||
if ( entry.type !== 'recipes' ) { continue; }
|
||||
if (
|
||||
entry.submitter === 'user' &&
|
||||
importedResourceKeys.has(assetKey) === false
|
||||
) {
|
||||
µm.assets.unregisterAssetSource(assetKey);
|
||||
µm.assets.remove(assetKey);
|
||||
continue;
|
||||
}
|
||||
availableRecipeFiles.set(assetKey, objectAssign({}, entry));
|
||||
}
|
||||
|
||||
for ( let asseyKey of µm.userSettings.selectedRecipeFiles ) {
|
||||
let asset = availableRecipeFiles.get(asseyKey);
|
||||
if ( asset !== undefined ) {
|
||||
asset.selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
callback(availableRecipeFiles);
|
||||
};
|
||||
|
||||
this.assets.metadata(onBuiltinRecipeFilesLoaded);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
µMatrix.loadHostsFiles = function(callback) {
|
||||
var µm = µMatrix;
|
||||
var hostsFileLoadCount;
|
||||
|
@ -287,7 +441,7 @@
|
|||
|
||||
var loadHostsFilesEnd = function() {
|
||||
µm.ubiquitousBlacklist.freeze();
|
||||
vAPI.storage.set({ 'liveHostsFiles': µm.liveHostsFiles });
|
||||
vAPI.storage.set({ liveHostsFiles: Array.from(µm.liveHostsFiles) });
|
||||
vAPI.messaging.broadcast({ what: 'loadHostsFilesCompleted' });
|
||||
µm.getBytesInUse();
|
||||
callback();
|
||||
|
@ -304,17 +458,11 @@
|
|||
var loadHostsFilesStart = function(hostsFiles) {
|
||||
µm.liveHostsFiles = hostsFiles;
|
||||
µm.ubiquitousBlacklist.reset();
|
||||
var locations = Object.keys(hostsFiles);
|
||||
hostsFileLoadCount = locations.length;
|
||||
hostsFileLoadCount = µm.userSettings.selectedHostsFiles.length;
|
||||
|
||||
// Load all hosts file which are not disabled.
|
||||
var location;
|
||||
while ( (location = locations.pop()) ) {
|
||||
if ( hostsFiles[location].off ) {
|
||||
hostsFileLoadCount -= 1;
|
||||
continue;
|
||||
}
|
||||
µm.assets.get(location, mergeHostsFile);
|
||||
for ( let assetKey of µm.userSettings.selectedHostsFiles ) {
|
||||
µm.assets.get(assetKey, mergeHostsFile);
|
||||
}
|
||||
|
||||
// https://github.com/gorhill/uMatrix/issues/2
|
||||
|
@ -338,9 +486,9 @@
|
|||
usedCount = this.ubiquitousBlacklist.count - usedCount;
|
||||
duplicateCount = this.ubiquitousBlacklist.duplicateCount - duplicateCount;
|
||||
|
||||
var hostsFilesMeta = this.liveHostsFiles[details.assetKey];
|
||||
hostsFilesMeta.entryCount = usedCount + duplicateCount;
|
||||
hostsFilesMeta.entryUsedCount = usedCount;
|
||||
let hostsFileMeta = this.liveHostsFiles.get(details.assetKey);
|
||||
hostsFileMeta.entryCount = usedCount + duplicateCount;
|
||||
hostsFileMeta.entryUsedCount = usedCount;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -402,63 +550,51 @@
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
// `switches` contains the filter lists for which the switch must be revisited.
|
||||
µMatrix.selectAssets = function(details, callback) {
|
||||
var µm = this;
|
||||
|
||||
µMatrix.selectHostsFiles = function(details, callback) {
|
||||
var µm = this,
|
||||
externalHostsFiles = this.userSettings.externalHostsFiles,
|
||||
i, n, assetKey;
|
||||
var applyAssetSelection = function(
|
||||
metadata,
|
||||
details,
|
||||
propSelectedAssetKeys,
|
||||
propImportedAssetKeys
|
||||
) {
|
||||
let µmus = µm.userSettings;
|
||||
let selectedAssetKeys = new Set();
|
||||
let importedAssetKeys = new Set(µmus[propImportedAssetKeys]);
|
||||
|
||||
// Hosts file to select
|
||||
if ( Array.isArray(details.toSelect) ) {
|
||||
for ( assetKey in this.liveHostsFiles ) {
|
||||
if ( this.liveHostsFiles.hasOwnProperty(assetKey) === false ) {
|
||||
continue;
|
||||
}
|
||||
if ( details.toSelect.indexOf(assetKey) !== -1 ) {
|
||||
this.liveHostsFiles[assetKey].off = false;
|
||||
} else if ( details.merge !== true ) {
|
||||
this.liveHostsFiles[assetKey].off = true;
|
||||
for ( let assetKey of details.toSelect ) {
|
||||
if ( metadata.has(assetKey) ) {
|
||||
selectedAssetKeys.add(assetKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Imported hosts files to remove
|
||||
if ( Array.isArray(details.toRemove) ) {
|
||||
var removeURLFromHaystack = function(haystack, needle) {
|
||||
return haystack.replace(
|
||||
new RegExp(
|
||||
'(^|\\n)' +
|
||||
needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +
|
||||
'(\\n|$)', 'g'),
|
||||
'\n'
|
||||
).trim();
|
||||
};
|
||||
for ( i = 0, n = details.toRemove.length; i < n; i++ ) {
|
||||
assetKey = details.toRemove[i];
|
||||
delete this.liveHostsFiles[assetKey];
|
||||
externalHostsFiles = removeURLFromHaystack(externalHostsFiles, assetKey);
|
||||
this.assets.remove(assetKey);
|
||||
for ( let assetKey of details.toRemove ) {
|
||||
importedAssetKeys.delete(assetKey);
|
||||
µm.assets.remove(assetKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Hosts file to import
|
||||
if ( typeof details.toImport === 'string' ) {
|
||||
// https://github.com/gorhill/uBlock/issues/1181
|
||||
// Try mapping the URL of an imported filter list to the assetKey of an
|
||||
// existing stock list.
|
||||
// Try mapping the URL of an imported filter list to the assetKey of
|
||||
// an existing stock list.
|
||||
if ( typeof details.toImport === 'string' ) {
|
||||
var assetKeyFromURL = function(url) {
|
||||
var needle = url.replace(/^https?:/, '');
|
||||
var assets = µm.liveHostsFiles, asset;
|
||||
for ( var assetKey in assets ) {
|
||||
asset = assets[assetKey];
|
||||
if ( asset.content !== 'filters' ) { continue; }
|
||||
for ( let entry of metadata ) {
|
||||
let asset = entry[1];
|
||||
if ( asset.type === 'internal' ) { continue; }
|
||||
let assetKey = entry[0];
|
||||
if ( typeof asset.contentURL === 'string' ) {
|
||||
if ( asset.contentURL.endsWith(needle) ) { return assetKey; }
|
||||
continue;
|
||||
}
|
||||
if ( Array.isArray(asset.contentURL) === false ) { continue; }
|
||||
for ( i = 0, n = asset.contentURL.length; i < n; i++ ) {
|
||||
for ( let i = 0, n = asset.contentURL.length; i < n; i++ ) {
|
||||
if ( asset.contentURL[i].endsWith(needle) ) {
|
||||
return assetKey;
|
||||
}
|
||||
|
@ -466,37 +602,73 @@
|
|||
}
|
||||
return url;
|
||||
};
|
||||
var importedSet = new Set(this.listKeysFromCustomHostsFiles(externalHostsFiles)),
|
||||
toImportSet = new Set(this.listKeysFromCustomHostsFiles(details.toImport)),
|
||||
iter = toImportSet.values();
|
||||
for (;;) {
|
||||
var entry = iter.next();
|
||||
if ( entry.done ) { break; }
|
||||
if ( importedSet.has(entry.value) ) { continue; }
|
||||
assetKey = assetKeyFromURL(entry.value);
|
||||
if ( assetKey === entry.value ) {
|
||||
importedSet.add(entry.value);
|
||||
var toImport = µm.assetKeysFromImportedAssets(details.toImport);
|
||||
for ( let url of toImport ) {
|
||||
if ( importedAssetKeys.has(url) ) { continue; }
|
||||
let assetKey = assetKeyFromURL(url);
|
||||
if ( assetKey === url ) {
|
||||
importedAssetKeys.add(assetKey);
|
||||
}
|
||||
this.liveHostsFiles[assetKey] = {
|
||||
content: 'filters',
|
||||
contentURL: [ assetKey ],
|
||||
title: assetKey
|
||||
};
|
||||
selectedAssetKeys.add(assetKey);
|
||||
}
|
||||
externalHostsFiles = this.setToArray(importedSet).sort().join('\n');
|
||||
}
|
||||
|
||||
if ( externalHostsFiles !== this.userSettings.externalHostsFiles ) {
|
||||
this.userSettings.externalHostsFiles = externalHostsFiles;
|
||||
vAPI.storage.set({ externalHostsFiles: externalHostsFiles });
|
||||
let bin = {},
|
||||
changed = false;
|
||||
|
||||
selectedAssetKeys = Array.from(selectedAssetKeys).sort();
|
||||
µmus[propSelectedAssetKeys].sort();
|
||||
if ( selectedAssetKeys.join() !== µmus[propSelectedAssetKeys].join() ) {
|
||||
µmus[propSelectedAssetKeys] = selectedAssetKeys;
|
||||
bin[propSelectedAssetKeys] = selectedAssetKeys;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
importedAssetKeys = Array.from(importedAssetKeys).sort();
|
||||
µmus[propImportedAssetKeys].sort();
|
||||
if ( importedAssetKeys.join() !== µmus[propImportedAssetKeys].join() ) {
|
||||
µmus[propImportedAssetKeys] = importedAssetKeys;
|
||||
bin[propImportedAssetKeys] = importedAssetKeys;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ( changed ) {
|
||||
vAPI.storage.set(bin);
|
||||
}
|
||||
|
||||
return changed;
|
||||
};
|
||||
|
||||
var onMetadataReady = function(response) {
|
||||
let metadata = µm.toMap(response);
|
||||
let hostsChanged = applyAssetSelection(
|
||||
metadata,
|
||||
details.hosts,
|
||||
'selectedHostsFiles',
|
||||
'externalHostsFiles'
|
||||
);
|
||||
let recipesChanged = applyAssetSelection(
|
||||
metadata,
|
||||
details.recipes,
|
||||
'selectedRecipeFiles',
|
||||
'externalRecipeFiles'
|
||||
);
|
||||
if ( recipesChanged ) {
|
||||
µm.recipeManager.reset();
|
||||
µm.loadRecipes(true);
|
||||
}
|
||||
vAPI.storage.set({ 'liveHostsFiles': this.liveHostsFiles });
|
||||
|
||||
if ( typeof callback === 'function' ) {
|
||||
callback();
|
||||
callback({
|
||||
hostsChanged: hostsChanged,
|
||||
recipesChanged: recipesChanged
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.assets.metadata(onMetadataReady);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// `switches` contains the preset blacklists for which the switch must be
|
||||
|
@ -533,6 +705,7 @@
|
|||
timer = undefined;
|
||||
}
|
||||
if ( updateDelay === 0 ) {
|
||||
this.assets.updateStop();
|
||||
next = 0;
|
||||
return;
|
||||
}
|
||||
|
@ -556,9 +729,13 @@
|
|||
µMatrix.assetObserver = function(topic, details) {
|
||||
// Do not update filter list if not in use.
|
||||
if ( topic === 'before-asset-updated' ) {
|
||||
let µmus = this.userSettings;
|
||||
if (
|
||||
this.liveHostsFiles.hasOwnProperty(details.assetKey) === false ||
|
||||
this.liveHostsFiles[details.assetKey].off !== true
|
||||
details.type === 'internal' ||
|
||||
details.type === 'filters' &&
|
||||
µmus.selectedHostsFiles.indexOf(details.assetKey) !== -1 ||
|
||||
details.type === 'recipes' &&
|
||||
µmus.selectedRecipeFiles.indexOf(details.assetKey) !== -1
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
@ -586,7 +763,20 @@
|
|||
|
||||
// Reload all filter lists if needed.
|
||||
if ( topic === 'after-assets-updated' ) {
|
||||
if ( details.assetKeys.length !== 0 ) {
|
||||
if (
|
||||
this.arraysIntersect(
|
||||
details.assetKeys,
|
||||
this.userSettings.selectedRecipeFiles
|
||||
)
|
||||
) {
|
||||
this.loadRecipes(true);
|
||||
}
|
||||
if (
|
||||
this.arraysIntersect(
|
||||
details.assetKeys,
|
||||
this.userSettings.selectedHostsFiles
|
||||
)
|
||||
) {
|
||||
this.loadHostsFiles();
|
||||
}
|
||||
if ( this.userSettings.autoUpdate ) {
|
||||
|
@ -600,15 +790,4 @@
|
|||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// New asset source became available, if it's a filter list, should we
|
||||
// auto-select it?
|
||||
if ( topic === 'builtin-asset-source-added' ) {
|
||||
if ( details.entry.content === 'filters' ) {
|
||||
if ( details.entry.off !== true ) {
|
||||
this.saveSelectedFilterLists([ details.assetKey ], true);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uMatrix - a Chromium browser extension to black/white list requests.
|
||||
Copyright (C) 2014-2017 Raymond Hill
|
||||
Copyright (C) 2014-2018 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
|
||||
|
@ -49,7 +49,9 @@
|
|||
|
||||
// Post-change
|
||||
switch ( name ) {
|
||||
|
||||
case 'autoUpdate':
|
||||
this.scheduleAssetUpdater(value === true ? 120000 : 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uMatrix - a Chromium browser extension to black/white list requests.
|
||||
Copyright (C) 2014-2017 Raymond Hill
|
||||
Copyright (C) 2014-2018 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
|
||||
|
@ -103,3 +103,32 @@
|
|||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
µMatrix.toMap = function(input) {
|
||||
if ( input instanceof Map ) {
|
||||
return input;
|
||||
}
|
||||
if ( Array.isArray(input) ) {
|
||||
return new Map(input);
|
||||
}
|
||||
let out = new Map();
|
||||
if ( input instanceof Object ) {
|
||||
for ( let key in input ) {
|
||||
if ( input.hasOwnProperty(key) ) {
|
||||
out.set(key, input[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
µMatrix.arraysIntersect = function(a1, a2) {
|
||||
for ( let v of a1 ) {
|
||||
if ( a2.indexOf(v) !== -1 ) { return true; }
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
@ -11,29 +11,6 @@
|
|||
|
||||
<body>
|
||||
|
||||
<div id="templates" style="display:none">
|
||||
<div class="groupSeparator"></div>
|
||||
<div class="domainSeparator"></div>
|
||||
<div class="matRow"><div class="matCell"><b> </b> </div><div class="matCell"> </div><div class="matCell"> </div><div class="matCell"> </div><div class="matCell"> </div><div class="matCell"> </div><div class="matCell"> </div><div class="matCell"> </div><div class="matCell"> </div></div>
|
||||
<div id="cellHotspots"><div id="whitelist"></div><div id="blacklist"></div><div id="domainOnly"><span class="fa"></span></div></div>
|
||||
<!-- Use once min supported browser version allows for use of CSS variables
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<symbol id="toggleButton" viewBox="0 0 152 96">
|
||||
<g>
|
||||
<path d="m 48,24 a 24,24 0 0 0 -24,24 24,24 0 0 0 24,24 l 48,0 A 24,24 0 0 0 120,48 24,24 0 0 0 96,24 l -48,0 z" style="opacity:1;fill:#bbb;fill-opacity:1;stroke:none;" />
|
||||
<g style="display:var(--off);">
|
||||
<ellipse style="fill:#bbb;fill-opacity:1;stroke:none;" cx="48" cy="48" rx="48" ry="48" />
|
||||
<ellipse style="opacity:1;fill:#fff;fill-opacity:1;stroke:none;" cx="48" cy="48" rx="40" ry="40" />
|
||||
<ellipse style="display:var(--dot);fill:#bbb;fill-opacity:1;stroke:none;" cx="48" cy="48" rx="12" ry="12" /></g>
|
||||
<g style="display:var(--on);">
|
||||
<ellipse style="opacity:1;fill:#444;fill-opacity:1;stroke:none;" cx="104" cy="48" rx="48" ry="48" />
|
||||
<ellipse style="display:var(--dot);fill:#bbb;fill-opacity:1;stroke:none;" cx="104" cy="48" rx="12" ry="12" /></g>
|
||||
</g>
|
||||
</symbol>
|
||||
</svg>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div class="paneHead">
|
||||
<a id="gotoDashboard" class="extensionURL" href="#" data-extension-url="dashboard.html" data-i18n-tip="matrixDashboardMenuEntry">uMatrix <span id="version"> </span><span class="fa"></span></a>
|
||||
<div id="toolbarContainer">
|
||||
|
@ -44,6 +21,9 @@
|
|||
<button id="buttonMtxSwitches" type="button" class="fa scopeRel" tabindex="-1" data-dropdown-menu="dropDownMenuSwitches"><span class="badge"></span></button>
|
||||
<button id="buttonPersist" type="button" class="fa scopeRel tip-anchor-left" data-i18n-tip="matrixPersistButtonTip"><span class="badge"></span></button>
|
||||
<button id="buttonRevertScope" type="button" class="fa scopeRel tip-anchor-left" tabindex="-1" data-i18n-tip="matrixRevertButtonTip"></button>
|
||||
<button id="buttonRecipes" type="button" class="fa scopeRel tip-anchor-right" data-dropdown-menu="dropDownMenuRecipes"><span class="badge"></span></button>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<button id="buttonReload" type="button" class="fa tip-anchor-left" data-i18n-tip="matrixReloadButton"></button>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
|
@ -94,6 +74,12 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="dropDownMenuRecipes" class="dropdown-menu-capture">
|
||||
<div class="dropdown-menu">
|
||||
<ul></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="noTabFound"></div>
|
||||
|
||||
<!-- Convenient to auto-fetch locale strings used in scripts -->
|
||||
|
@ -104,6 +90,30 @@
|
|||
|
||||
<div style="clear: both; height: 0px;"></div>
|
||||
|
||||
<div id="templates" style="display:none">
|
||||
<div class="groupSeparator"></div>
|
||||
<div class="domainSeparator"></div>
|
||||
<div class="matRow"><div class="matCell"><b> </b> </div><div class="matCell"> </div><div class="matCell"> </div><div class="matCell"> </div><div class="matCell"> </div><div class="matCell"> </div><div class="matCell"> </div><div class="matCell"> </div><div class="matCell"> </div></div>
|
||||
<div id="cellHotspots"><div id="whitelist"></div><div id="blacklist"></div><div id="domainOnly"><span class="fa"></span></div></div>
|
||||
<!-- Use once min supported browser version allows for use of CSS variables
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<symbol id="toggleButton" viewBox="0 0 152 96">
|
||||
<g>
|
||||
<path d="m 48,24 a 24,24 0 0 0 -24,24 24,24 0 0 0 24,24 l 48,0 A 24,24 0 0 0 120,48 24,24 0 0 0 96,24 l -48,0 z" style="opacity:1;fill:#bbb;fill-opacity:1;stroke:none;" />
|
||||
<g style="display:var(--off);">
|
||||
<ellipse style="fill:#bbb;fill-opacity:1;stroke:none;" cx="48" cy="48" rx="48" ry="48" />
|
||||
<ellipse style="opacity:1;fill:#fff;fill-opacity:1;stroke:none;" cx="48" cy="48" rx="40" ry="40" />
|
||||
<ellipse style="display:var(--dot);fill:#bbb;fill-opacity:1;stroke:none;" cx="48" cy="48" rx="12" ry="12" /></g>
|
||||
<g style="display:var(--on);">
|
||||
<ellipse style="opacity:1;fill:#444;fill-opacity:1;stroke:none;" cx="104" cy="48" rx="48" ry="48" />
|
||||
<ellipse style="display:var(--dot);fill:#bbb;fill-opacity:1;stroke:none;" cx="104" cy="48" rx="12" ry="12" /></g>
|
||||
</g>
|
||||
</symbol>
|
||||
</svg>
|
||||
-->
|
||||
<li class="recipe"><div><span class="expander"></span><span class="name"></span><span class="fa committer"></span></div><div class="ruleset"></div></li>
|
||||
</div>
|
||||
|
||||
<script src="lib/punycode.js"></script>
|
||||
<script src="js/vapi-common.js"></script>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
|
|
|
@ -8,6 +8,7 @@ printf "*** Packaging assets in $DES... "
|
|||
|
||||
rm -rf $DES
|
||||
mkdir $DES
|
||||
|
||||
cp ./assets/assets.json $DES/
|
||||
|
||||
if [ -n "${TRAVIS_TAG}" ]; then
|
||||
|
@ -24,5 +25,7 @@ cp -R ../uAssets/thirdparties/publicsuffix.org $DES/thirdparties/
|
|||
cp -R ../uAssets/thirdparties/someonewhocares.org $DES/thirdparties/
|
||||
cp -R ../uAssets/thirdparties/winhelp2002.mvps.org $DES/thirdparties/
|
||||
cp -R ../uAssets/thirdparties/www.malwaredomainlist.com $DES/thirdparties/
|
||||
mkdir $DES/umatrix
|
||||
cp -R ../uAssets/recipes/* $DES/umatrix/
|
||||
|
||||
echo "done."
|
||||
|
|
Loading…
Reference in a new issue