make use of CodeMirror in "My rules" pane

This commit is contained in:
Raymond Hill 2018-03-11 10:59:39 -04:00
parent caef7d00bb
commit b10ac0020d
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
9 changed files with 593 additions and 607 deletions

View File

@ -24,6 +24,7 @@
<li><a href="https://github.com/bestiejs/punycode.js" target="_blank">Punycode.js</a> by <a href="https://github.com/mathiasbynens">Mathias Bynens</a>
<li><a href="https://fontawesome.com/" target="_blank">Font Awesome</a> by <a href="https://github.com/davegandy">Dave Gandy</a>
<li><a href="https://codemirror.net/" target="_blank">CodeMirror</a> by <a href="https://github.com/marijnh">Marijn Haverbeke</a>
<li><a href="https://github.com/Swatinem/diff" target="_blank">An implementation of Myers' diff algorithm</a> by <a href="https://github.com/Swatinem">Arpad Borsos</a>
</ul>
<script src="js/vapi.js"></script>

View File

@ -12,7 +12,7 @@
}
/* For when panels are used */
.codeMirrorContainer > div:not(.CodeMirror) {
.codeMirrorContainer > div:not([class^="CodeMirror"]) {
display: flex;
flex-direction: column;
height: 100%;
@ -63,3 +63,12 @@
.cm-search-widget .cm-search-widget-button:hover {
color: #000;
}
.CodeMirror-merge-l-deleted {
background-image: none;
font-weight: bold;
}
.CodeMirror-merge-l-inserted {
background-image: none;
font-weight: bold;
}

View File

@ -1,38 +1,40 @@
div > p:first-child {
margin-top: 0;
body {
bottom: 0;
display: flex;
left: 0;
position: absolute;
right: 0;
top: 0;
flex-direction: column;
}
div > p:last-child {
margin-bottom: 0;
}
code {
background-color: #eee;
font: 11px monospace;
padding: 2px 4px;
}
#diff {
border: 0;
border-top: 1px solid #eee;
flex-grow: 1;
margin: 0;
padding: 0.5em 0 0 0;
padding: 0;
white-space: nowrap;
}
#diff .pane {
#diff .tools > * {
margin-bottom: 0.5em;
}
#diff .tools .fa {
font-size: large;
}
#diff .ruleActions {
border: 0;
box-sizing: border-box;
display: inline-block;
font: 90%/180% "Noto Mono",monospace;
margin: 0;
padding: 0;
position: relative;
white-space: normal;
text-align: center;
vertical-align: top;
width: 50%;
}
#diff .pane .rulesContainer {
position: relative;
min-height: 150px; /* too short is confusing */
#diff .ruleActions h3 {
font-weight: normal;
margin: 0.5em 0;
}
#diff .ruleActions {
padding: 0 0 1em 0;
#diff .ruleFilter {
text-align: center;
}
body[dir="ltr"] #revertButton:after {
@ -73,97 +75,33 @@ body[dir="rtl"] #commitButton:before {
}
#revertButton,
#commitButton,
#diff.edit #editEnterButton {
#diff.editing #exportButton,
#diff.editing #importButton,
#editSaveButton {
opacity: 0.25;
pointer-events: none;
}
#editStopButton,
#editCancelButton {
display: none;
}
#diff.dirty:not(.edit) #revertButton,
#diff.dirty:not(.edit) #commitButton {
#diff.dirty:not(.editing) #revertButton,
#diff.dirty:not(.editing) #commitButton,
#diff.editing #editSaveButton {
opacity: 1;
pointer-events: auto;
}
#diff.edit #editStopButton,
#diff.edit #editCancelButton {
display: initial;
.codeMirrorContainer {
height: 60vh;
}
#diff.edit #importButton,
#diff.edit #exportButton {
.CodeMirror-merge, .CodeMirror-merge-pane, .CodeMirror-merge .CodeMirror {
box-sizing: border-box;
height: 100%;
}
#diff.editing .CodeMirror-merge-copy,
#diff.editing .CodeMirror-merge-copy-reverse {
display: none;
}
#diff ul {
border: 0;
border-top: 1px solid #eee;
list-style-type: none;
margin: 0;
overflow: hidden;
padding: 1em 0 0 0;
}
#diff.edit .right ul {
visibility: hidden;
}
#diff .left {
border-right: 1px solid #eee;
}
#diff .right > ul {
#diff.editing .CodeMirror-merge-left .CodeMirror {
color: #888;
}
#diff li {
background-color: #ddd;
direction: ltr;
padding: 0;
text-align: left;
white-space: nowrap;
padding-left: 3px; /* a bit of padding; must also be in textarea */
}
#diff li:nth-child(even) {
background-color: #eee;
}
#diff .right li {
opacity: 0.5;
}
#diff .right li:hover {
}
#diff .right li.notLeft {
color: #000;
opacity: 1;
}
#diff .right li.notRight {
color: #000;
}
#diff .right li.toRemove {
color: #000;
text-decoration: line-through;
opacity: 1;
}
#diff textarea {
background-color: #f8f8ff;
border: 0;
border-top: 1px solid #eee;
box-sizing: border-box;
color: black;
direction: ltr;
font: inherit;
height: 100%;
left: 0;
margin: 0;
overflow: hidden;
overflow-y: auto;
padding: 1em 0 0 3px; /* same left and top padding as ul/li */
position: absolute;
resize: none;
top: 0;
visibility: hidden;
white-space: pre; /* this implies nowrap; break only on \n and <br>.
nowrap doesn't consistently
respect \n's (example: Safari) per the CSS spec:
http://www.w3.org/wiki/CSS/Properties/white-space */
width: 100%;
word-wrap: normal;
}
#diff.edit textarea {
visibility: visible;
#diff.editing .CodeMirror-merge-editor .CodeMirror {
background-color: #ffe;
}

View File

@ -4,51 +4,49 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>uBlock — Dynamic filtering rules</title>
<link rel="stylesheet" type="text/css" href="lib/codemirror/lib/codemirror.css">
<link rel="stylesheet" type="text/css" href="lib/codemirror/addon/dialog/dialog.css">
<link rel="stylesheet" type="text/css" href="lib/codemirror/addon/merge/merge.css">
<link rel="stylesheet" type="text/css" href="css/common.css">
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
<link rel="stylesheet" type="text/css" href="css/cloud-ui.css">
<link rel="stylesheet" type="text/css" href="css/dyna-rules.css">
<link rel="stylesheet" type="text/css" href="css/codemirror.css">
</head>
<body>
<div id="cloudWidget" class="hide" data-cloud-entry="myRulesPane"></div>
<p data-i18n="rulesHint"></p>
<p data-i18n="rulesFormatHint"></p>
<p><span data-i18n="rulesHint"></span> <a class="fa info" href="https://github.com/gorhill/uBlock/wiki/Dynamic-filtering:-rule-syntax" target="_blank">&#xf05a;</a></p>
<div id="diff">
<div class="pane left">
<div class="tools">
<div class="ruleActions">
<h2 data-i18n="rulesPermanentHeader"></h2>
<h3 data-i18n="rulesPermanentHeader"></h3>
<button type="button" class="custom" id="exportButton" data-i18n="rulesExport"></button>
<button type="button" class="custom" id="revertButton" data-i18n="rulesRevert"></button>
</div>
<div class="rulesContainer">
<ul></ul>
</div>
</div>
<div class="pane right">
<div class="ruleActions">
<h2 data-i18n="rulesTemporaryHeader"></h2>
<h3 data-i18n="rulesTemporaryHeader"></h3>
<button type="button" class="custom" id="commitButton" data-i18n="rulesCommit"></button>
<button type="button" class="custom" id="editEnterButton" data-i18n="rulesEdit"></button>
<button type="button" class="custom" id="editStopButton" data-i18n="rulesEditSave"></button>
<button type="button" class="custom" id="editCancelButton" data-i18n="rulesEditDiscard"></button>
<button type="button" class="custom" id="importButton" data-i18n="rulesImport"></button>
<button type="button" class="custom" id="editSaveButton" data-i18n="rulesEditSave"></button>
</div>
<div class="rulesContainer">
<textarea spellcheck="false"></textarea>
<ul></ul>
</div>
<!-- TO BE IMPLEMENTED: <div class="ruleFilter"><span class="fa">&#xf0b0;</span>&emsp;<input type="text" size="32"></div> -->
</div>
<div class="codeMirrorContainer codeMirrorMergeContainer"></div>
</div>
<div id="templates" style="display: none;">
<input class="hidden" id="importFilePicker" type="file" accept="text/plain">
<span class="hidden" data-i18n="rulesDefaultFileName"></span>
<ul>
<li>&nbsp;</li>
</ul>
</div>
</div>
<script src="lib/diff/swatinem_diff.js"></script>
<script src="lib/codemirror/lib/codemirror.js"></script>
<script src="lib/codemirror/addon/merge/merge.js"></script>
<script src="lib/codemirror/addon/selection/active-line.js"></script>
<script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script>

View File

@ -1,7 +1,7 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2014-2016 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
@ -19,90 +19,142 @@
Home: https://github.com/gorhill/uMatrix
*/
/* global uDom, uBlockDashboard */
/******************************************************************************/
(function() {
/* global diff_match_patch, CodeMirror, uDom, uBlockDashboard */
'use strict';
/******************************************************************************/
var messaging = vAPI.messaging;
(function() {
/******************************************************************************/
var renderRules = function(details) {
var liTemplate = uDom('#templates > ul > li');
var ulLeft = uDom('#diff > .left ul').empty().remove();
var ulRight = uDom('#diff > .right ul').empty().remove();
var liLeft, liRight;
var rules, rule, i;
var messaging = vAPI.messaging;
// Switches always displayed first -- just like in uMatrix
// Merge url rules and switches: they just look the same
rules = details.hnSwitches.split(/\n+/).sort();
for ( i = 0; i < rules.length; i++ ) {
rule = rules[i];
liLeft = liTemplate.clone().text(rule);
liRight = liTemplate.clone().text(rule);
ulLeft.append(liLeft);
ulRight.append(liRight);
var mergeView = new CodeMirror.MergeView(
document.querySelector('.codeMirrorMergeContainer'),
{
allowEditingOriginals: true,
connect: 'align', // size of svg is not managed properly with `true`
inputStyle: 'contenteditable',
lineNumbers: true,
lineWrapping: false,
origLeft: '',
revertButtons: true,
value: ''
}
);
mergeView.editor().setOption('styleActiveLine', true);
mergeView.editor().setOption('lineNumbers', false);
mergeView.leftOriginal().setOption('readOnly', 'nocursor');
// Firewall rules follow
var allRules = {};
var permanentRules = {};
var sessionRules = {};
var onLeft, onRight;
var cleanToken = 0;
var cleanEditText = '';
rules = details.sessionRules.split(/\n+/);
i = rules.length;
var differ;
/******************************************************************************/
// Incrementally update text in a CodeMirror editor for best user experience:
// - Scroll position preserved
// - Minimum amount of text updated
var rulesToDoc = function(doc, rules) {
if ( doc.getValue() === '' || rules.length === 0 ) {
doc.setValue(rules.length !== 0 ? rules.join('\n') : '');
return;
}
if ( differ === undefined ) { differ = new diff_match_patch(); }
var beforeText = doc.getValue();
var afterText = rules.join('\n');
var diffs = differ.diff_main(beforeText, afterText);
doc.startOperation();
var i = diffs.length,
iedit = beforeText.length;
while ( i-- ) {
rule = rules[i].trim();
if ( rule === '' ) {
var diff = diffs[i];
if ( diff[0] === 0 ) {
iedit -= diff[1].length;
continue;
}
sessionRules[rule] = allRules[rule] = true;
}
details.sessionRules = rules.sort().join('\n');
rules = details.permanentRules.split(/\n+/);
i = rules.length;
while ( i-- ) {
rule = rules[i].trim();
if ( rule === '' ) {
var end = doc.posFromIndex(iedit);
if ( diff[0] === 1 ) {
doc.replaceRange(diff[1], end, end);
continue;
}
permanentRules[rule] = allRules[rule] = true;
/* diff[0] === -1 */
iedit -= diff[1].length;
var beg = doc.posFromIndex(iedit);
doc.replaceRange('', beg, end);
}
details.permanentRules = rules.sort().join('\n');
doc.endOperation();
};
rules = Object.keys(allRules).sort();
for ( i = 0; i < rules.length; i++ ) {
rule = rules[i];
onLeft = permanentRules.hasOwnProperty(rule);
onRight = sessionRules.hasOwnProperty(rule);
liLeft = liTemplate.clone();
liRight = liTemplate.clone();
if ( onLeft && onRight ) {
liLeft.text(rule);
liRight.text(rule);
} else if ( onLeft ) {
liLeft.text(rule);
liRight.text(rule).addClass('notRight toRemove');
} else {
liRight.text(rule).addClass('notLeft');
/******************************************************************************/
var renderRules = (function() {
var firstVisit = true;
return function(details) {
details.hnSwitches.sort();
details.permanentRules.sort();
details.sessionRules.sort();
var orig = details.hnSwitches.concat(details.permanentRules),
edit = details.hnSwitches.concat(details.sessionRules);
rulesToDoc(mergeView.leftOriginal(), orig);
rulesToDoc(mergeView.editor(), edit);
cleanEditText = mergeView.editor().getValue().trim();
if ( firstVisit ) {
mergeView.editor().clearHistory();
firstVisit = false;
mergeView.editor().execCommand('goNextDiff');
}
ulLeft.append(liLeft);
ulRight.append(liRight);
}
cleanToken = mergeView.editor().changeGeneration();
onChange(true);
};
})();
uDom('#diff > .left > .rulesContainer').append(ulLeft);
uDom('#diff > .right > .rulesContainer').append(ulRight);
uDom('#diff').toggleClass('dirty', details.sessionRules !== details.permanentRules);
/******************************************************************************/
var applyDiff = function(permanent, toAdd, toRemove, callback) {
messaging.send(
'dashboard',
{
what: 'modifyRuleset',
permanent: permanent,
toAdd: toAdd,
toRemove: toRemove
},
callback
);
};
/******************************************************************************/
// CodeMirror quirk: sometimes fromStart.ch and/or toStart.ch is undefined.
// When this happens, use 0.
mergeView.options.revertChunk = function(
mv,
from, fromStart, fromEnd,
to, toStart, toEnd
) {
if ( typeof fromStart.ch !== 'number' ) { fromStart.ch = 0; }
if ( fromEnd.ch !== 0 ) { fromEnd.line += 1; }
var toAdd = from.getRange(
{ line: fromStart.line, ch: 0 },
{ line: fromEnd.line, ch: 0 }
);
if ( typeof toStart.ch !== 'number' ) { toStart.ch = 0; }
if ( toEnd.ch !== 0 ) { toEnd.line += 1; }
var toRemove = to.getRange(
{ line: toStart.line, ch: 0 },
{ line: toEnd.line, ch: 0 }
);
applyDiff(from === mv.editor(), toAdd, toRemove);
to.replaceRange(toAdd, toStart, toEnd);
cleanToken = mergeView.editor().changeGeneration();
cleanEditText = mergeView.editor().getValue().trim();
};
/******************************************************************************/
@ -121,19 +173,11 @@ function handleImportFilePicker() {
.replace(/\|/g, ' ')
.replace(/\n/g, ' * noop\n');
}
var request = {
'what': 'setSessionRules',
'rules': rulesFromHTML('#diff .right li') + '\n' + result
};
messaging.send('dashboard', request, renderRules);
applyDiff(false, result, '', renderRules);
};
var file = this.files[0];
if ( file === undefined || file.name === '' ) {
return;
}
if ( file.type.indexOf('text') !== 0 ) {
return;
}
if ( file === undefined || file.name === '' ) { return; }
if ( file.type.indexOf('text') !== 0 ) { return; }
var fr = new FileReader();
fr.onload = fileReaderOnLoadHandler;
fr.readAsText(file);
@ -157,98 +201,137 @@ function exportUserRulesToFile() {
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
.replace(/ +/g, '_');
vAPI.download({
'url': 'data:text/plain,' + encodeURIComponent(rulesFromHTML('#diff .left li') + '\n'),
'filename': filename,
'saveAs': true
url: 'data:text/plain,' + encodeURIComponent(
mergeView.leftOriginal().getValue().trim() + '\n'
),
filename: filename,
saveAs: true
});
}
/******************************************************************************/
var rulesFromHTML = function(selector) {
var rules = [];
var lis = uDom(selector);
var li;
for ( var i = 0; i < lis.length; i++ ) {
li = lis.at(i);
if ( li.hasClassName('toRemove') ) {
rules.push('');
} else {
rules.push(li.text());
/*
var onFilter = (function() {
var timer;
var process = function() {
timer = undefined;
};
return function() {
if ( timer !== undefined ) { clearTimeout(timer); }
timer = vAPI.setTimeout(process, 577);
};
})();
*/
/******************************************************************************/
var onChange = (function() {
var timer;
var process = function(now) {
timer = undefined;
var isClean = mergeView.editor().isClean(cleanToken);
var diff = document.getElementById('diff');
if (
now &&
isClean === false &&
mergeView.editor().getValue().trim() === cleanEditText
) {
cleanToken = mergeView.editor().changeGeneration();
isClean = true;
}
diff.classList.toggle('editing', isClean === false);
diff.classList.toggle('dirty', mergeView.leftChunks().length !== 0);
CodeMirror.commands.save = isClean ? undefined : editSaveHandler;
};
return function(now) {
if ( timer !== undefined ) { clearTimeout(timer); }
timer = now ? process(now) : vAPI.setTimeout(process, 57);
};
})();
/******************************************************************************/
var revertAllHandler = function() {
var toAdd = [], toRemove = [];
var left = mergeView.leftOriginal(),
edit = mergeView.editor();
for ( var chunk of mergeView.leftChunks() ) {
var addedLines = left.getRange(
{ line: chunk.origFrom, ch: 0 },
{ line: chunk.origTo, ch: 0 }
);
var removedLines = edit.getRange(
{ line: chunk.editFrom, ch: 0 },
{ line: chunk.editTo, ch: 0 }
);
toAdd.push(addedLines.trim());
toRemove.push(removedLines.trim());
}
return rules.join('\n').trim();
applyDiff(false, toAdd.join('\n'), toRemove.join('\n'), renderRules);
};
/******************************************************************************/
var revertHandler = function() {
var request = {
'what': 'setSessionRules',
'rules': rulesFromHTML('#diff .left li')
};
messaging.send('dashboard', request, renderRules);
var commitAllHandler = function() {
var toAdd = [], toRemove = [];
var left = mergeView.leftOriginal(),
edit = mergeView.editor();
for ( var chunk of mergeView.leftChunks() ) {
var addedLines = edit.getRange(
{ line: chunk.editFrom, ch: 0 },
{ line: chunk.editTo, ch: 0 }
);
var removedLines = left.getRange(
{ line: chunk.origFrom, ch: 0 },
{ line: chunk.origTo, ch: 0 }
);
toAdd.push(addedLines.trim());
toRemove.push(removedLines.trim());
}
applyDiff(true, toAdd.join('\n'), toRemove.join('\n'), renderRules);
};
/******************************************************************************/
var commitHandler = function() {
var request = {
'what': 'setPermanentRules',
'rules': rulesFromHTML('#diff .right li')
};
messaging.send('dashboard', request, renderRules);
};
/******************************************************************************/
var editStartHandler = function() {
var parent = uDom(this).ancestors('#diff');
// If we're already editing, don't reset
if ( parent.hasClassName('edit') ) {
var editSaveHandler = function() {
var editor = mergeView.editor();
var editText = editor.getValue().trim();
if ( editText === cleanEditText ) {
onChange(true);
return;
}
uDom('#diff .right textarea').val(rulesFromHTML('#diff .right li'));
parent.toggleClass('edit', true);
};
/******************************************************************************/
var editStopHandler = function() {
var parent = uDom(this).ancestors('#diff');
parent.toggleClass('edit', false);
var request = {
'what': 'setSessionRules',
'rules': uDom('#diff .right textarea').val()
};
messaging.send('dashboard', request, renderRules);
};
/******************************************************************************/
var editCancelHandler = function() {
var parent = uDom(this).ancestors('#diff');
parent.toggleClass('edit', false);
if ( differ === undefined ) { differ = new diff_match_patch(); }
var toAdd = [], toRemove = [];
var diffs = differ.diff_main(cleanEditText, editText);
for ( var diff of diffs ) {
if ( diff[0] === 1 ) {
toAdd.push(diff[1]);
} else if ( diff[0] === -1 ) {
toRemove.push(diff[1]);
}
}
applyDiff(false, toAdd.join(''), toRemove.join(''), renderRules);
};
/******************************************************************************/
var getCloudData = function() {
return rulesFromHTML('#diff .left li');
return mergeView.leftOriginal().getValue().trim();
};
var setCloudData = function(data, append) {
if ( typeof data !== 'string' ) {
return;
}
if ( append ) {
data = rulesFromHTML('#diff .right li') + '\n' + data;
}
var request = {
'what': 'setSessionRules',
'rules': data
};
messaging.send('dashboard', request, renderRules);
if ( typeof data !== 'string' ) { return; }
applyDiff(
false,
data,
append ? '' : mergeView.editor().getValue().trim(),
renderRules
);
};
self.cloud.onPush = getCloudData;
@ -256,19 +339,18 @@ self.cloud.onPull = setCloudData;
/******************************************************************************/
messaging.send('dashboard', { what: 'getRules' }, renderRules);
// Handle user interaction
uDom('#importButton').on('click', startImportFilePicker);
uDom('#importFilePicker').on('change', handleImportFilePicker);
uDom('#exportButton').on('click', exportUserRulesToFile);
uDom('#revertButton').on('click', revertAllHandler);
uDom('#commitButton').on('click', commitAllHandler);
uDom('#editSaveButton').on('click', editSaveHandler);
uDom('#revertButton').on('click', revertHandler);
uDom('#commitButton').on('click', commitHandler);
uDom('#editEnterButton').on('click', editStartHandler);
uDom('#diff > .pane.right > .rulesContainer').on('dblclick', editStartHandler);
uDom('#editStopButton').on('click', editStopHandler);
uDom('#editCancelButton').on('click', editCancelHandler);
messaging.send('dashboard', { what: 'getRules' }, renderRules);
// https://groups.google.com/forum/#!topic/codemirror/UQkTrt078Vs
mergeView.editor().on('updateDiff', function() { onChange(); });
/******************************************************************************/

View File

@ -1,7 +1,7 @@
/*******************************************************************************
uBlock Origin - a browser extension to block 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
@ -30,10 +30,6 @@
/******************************************************************************/
var magicId = 'chmdgxwtetgu';
/******************************************************************************/
var Matrix = function() {
this.reset();
};
@ -77,6 +73,7 @@ var nameToActionMap = {
var reHostnameVeryCoarse = /[g-z_-]/;
var reIPv4VeryCoarse = /\.\d+$/;
var reBadHostname = /[^0-9a-z_.\[\]:%-]/;
var reNotASCII = /[^\x20-\x7F]/;
// http://tools.ietf.org/html/rfc5952
// 4.3: "MUST be represented in lowercase"
@ -115,78 +112,69 @@ Matrix.prototype.reset = function() {
this.type = '';
this.y = '';
this.z = '';
this.rules = {};
this.rules = new Map();
this.changed = false;
};
/******************************************************************************/
Matrix.prototype.assign = function(other) {
var thisRules = this.rules;
var otherRules = other.rules;
var k;
// Remove rules not in other
for ( k in thisRules ) {
if ( thisRules.hasOwnProperty(k) === false ) {
continue;
}
if ( otherRules.hasOwnProperty(k) === false ) {
delete thisRules[k];
for ( var k of this.rules.keys() ) {
if ( other.rules.has(k) === false ) {
this.rules.delete(k);
this.changed = true;
}
}
// Add/change rules in other
for ( k in otherRules ) {
if ( otherRules.hasOwnProperty(k) === false ) {
continue;
for ( var entry of other.rules ) {
if ( this.rules.get(entry[0]) !== entry[1] ) {
this.rules.set(entry[0], entry[1]);
this.changed = true;
}
thisRules[k] = otherRules[k];
}
};
/******************************************************************************/
Matrix.prototype.copyRules = function(other, srcHostname, desHostnames) {
var thisRules = this.rules;
var otherRules = other.rules;
var ruleKey, ruleValue;
// Specific types
ruleValue = otherRules['* *'] || 0;
if ( ruleValue !== 0 ) {
thisRules['* *'] = ruleValue;
var bits = other.rules.get('* *');
if ( bits !== undefined ) {
this.rules.set('* *', bits);
} else {
delete thisRules['* *'];
this.rules.delete('* *');
}
ruleKey = srcHostname + ' *';
ruleValue = otherRules[ruleKey] || 0;
if ( ruleValue !== 0 ) {
thisRules[ruleKey] = ruleValue;
var key = srcHostname + ' *';
bits = other.rules.get(key);
if ( bits !== undefined ) {
this.rules.set(key, bits);
} else {
delete thisRules[ruleKey];
this.rules.delete(key);
}
// Specific destinations
for ( var desHostname in desHostnames ) {
if ( desHostnames.hasOwnProperty(desHostname) === false ) {
continue;
}
ruleKey = '* ' + desHostname;
ruleValue = otherRules[ruleKey] || 0;
if ( ruleValue !== 0 ) {
thisRules[ruleKey] = ruleValue;
if ( desHostnames.hasOwnProperty(desHostname) === false ) { continue; }
key = '* ' + desHostname;
bits = other.rules.get(key);
if ( bits !== undefined ) {
this.rules.set(key, bits);
} else {
delete thisRules[ruleKey];
this.rules.delete(key);
}
ruleKey = srcHostname + ' ' + desHostname ;
ruleValue = otherRules[ruleKey] || 0;
if ( ruleValue !== 0 ) {
thisRules[ruleKey] = ruleValue;
key = srcHostname + ' ' + desHostname ;
bits = other.rules.get(key);
if ( bits !== undefined ) {
this.rules.set(key, bits);
} else {
delete thisRules[ruleKey];
this.rules.delete(key);
}
}
this.changed = true;
return true;
};
@ -198,28 +186,25 @@ Matrix.prototype.copyRules = function(other, srcHostname, desHostnames) {
// - from to *
Matrix.prototype.hasSameRules = function(other, srcHostname, desHostnames) {
var thisRules = this.rules;
var otherRules = other.rules;
var ruleKey;
// Specific types
ruleKey = '* *';
if ( (thisRules[ruleKey] || 0) !== (otherRules[ruleKey] || 0) ) {
var key = '* *';
if ( this.rules.get(key) !== other.rules.get(key) ) {
return false;
}
ruleKey = srcHostname + ' *';
if ( (thisRules[ruleKey] || 0) !== (otherRules[ruleKey] || 0) ) {
key = srcHostname + ' *';
if ( this.rules.get(key) !== other.rules.get(key) ) {
return false;
}
// Specific destinations
for ( var desHostname in desHostnames ) {
ruleKey = '* ' + desHostname;
if ( (thisRules[ruleKey] || 0) !== (otherRules[ruleKey] || 0) ) {
key = '* ' + desHostname;
if ( this.rules.get(key) !== other.rules.get(key) ) {
return false;
}
ruleKey = srcHostname + ' ' + desHostname ;
if ( (thisRules[ruleKey] || 0) !== (otherRules[ruleKey] || 0) ) {
key = srcHostname + ' ' + desHostname ;
if ( this.rules.get(key) !== other.rules.get(key) ) {
return false;
}
}
@ -232,19 +217,17 @@ Matrix.prototype.hasSameRules = function(other, srcHostname, desHostnames) {
Matrix.prototype.setCell = function(srcHostname, desHostname, type, state) {
var bitOffset = typeBitOffsets[type];
var k = srcHostname + ' ' + desHostname;
var oldBitmap = this.rules[k];
if ( oldBitmap === undefined ) {
oldBitmap = 0;
}
var oldBitmap = this.rules.get(k) || 0;
var newBitmap = oldBitmap & ~(3 << bitOffset) | (state << bitOffset);
if ( newBitmap === oldBitmap ) {
return false;
}
if ( newBitmap === 0 ) {
delete this.rules[k];
this.rules.delete(k);
} else {
this.rules[k] = newBitmap;
this.rules.set(k, newBitmap);
}
this.changed = true;
return true;
};
@ -256,6 +239,7 @@ Matrix.prototype.unsetCell = function(srcHostname, desHostname, type) {
return false;
}
this.setCell(srcHostname, desHostname, type, 0);
this.changed = true;
return true;
};
@ -265,7 +249,7 @@ Matrix.prototype.unsetCell = function(srcHostname, desHostname, type) {
Matrix.prototype.evaluateCell = function(srcHostname, desHostname, type) {
var key = srcHostname + ' ' + desHostname;
var bitmap = this.rules[key];
var bitmap = this.rules.get(key);
if ( bitmap === undefined ) {
return 0;
}
@ -314,7 +298,7 @@ Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type, broade
var v;
for (;;) {
this.z = s;
v = this.rules[s + ' ' + desHostname];
v = this.rules.get(s + ' ' + desHostname);
if ( v !== undefined ) {
v = v >>> bitOffset & 3;
if ( v !== 0 ) {
@ -478,22 +462,15 @@ Matrix.prototype.desHostnameFromRule = function(rule) {
/******************************************************************************/
Matrix.prototype.toString = function() {
Matrix.prototype.toArray = function() {
var out = [],
rule, type, val,
srcHostname, desHostname,
toUnicode = punycode.toUnicode;
for ( rule in this.rules ) {
if ( this.rules.hasOwnProperty(rule) === false ) {
continue;
}
srcHostname = this.srcHostnameFromRule(rule);
desHostname = this.desHostnameFromRule(rule);
for ( type in typeBitOffsets ) {
if ( typeBitOffsets.hasOwnProperty(type) === false ) {
continue;
}
val = this.evaluateCell(srcHostname, desHostname, type);
for ( var key of this.rules.keys() ) {
var srcHostname = this.srcHostnameFromRule(key);
var desHostname = this.desHostnameFromRule(key);
for ( var type in typeBitOffsets ) {
if ( typeBitOffsets.hasOwnProperty(type) === false ) { continue; }
var val = this.evaluateCell(srcHostname, desHostname, type);
if ( val === 0 ) { continue; }
if ( srcHostname.indexOf('xn--') !== -1 ) {
srcHostname = toUnicode(srcHostname);
@ -509,108 +486,92 @@ Matrix.prototype.toString = function() {
);
}
}
return out.join('\n');
return out;
};
Matrix.prototype.toString = function() {
return this.toArray().join('\n');
};
/******************************************************************************/
Matrix.prototype.fromString = function(text, append) {
var lineIter = new µBlock.LineIterator(text),
line, pos, fields,
srcHostname, desHostname, type, action,
reNotASCII = /[^\x20-\x7F]/,
toASCII = punycode.toASCII;
if ( append !== true ) {
this.reset();
}
var lineIter = new µBlock.LineIterator(text);
if ( append !== true ) { this.reset(); }
while ( lineIter.eot() === false ) {
line = lineIter.next().trim();
pos = line.indexOf('# ');
if ( pos !== -1 ) {
line = line.slice(0, pos).trim();
}
if ( line === '' ) {
continue;
}
// URL net filtering rules
if ( line.indexOf('://') !== -1 ) {
continue;
}
// Valid rule syntax:
// srcHostname desHostname type state
// type = a valid request type
// state = [`block`, `allow`, `noop`]
// Lines with invalid syntax silently ignored
fields = line.split(/\s+/);
if ( fields.length !== 4 ) {
continue;
}
// Ignore special rules:
// hostname-based switch rules
if ( fields[0].endsWith(':') ) {
continue;
}
// Performance: avoid punycoding if hostnames are made only of
// ASCII characters.
srcHostname = fields[0];
if ( reNotASCII.test(srcHostname) ) {
srcHostname = toASCII(srcHostname);
}
desHostname = fields[1];
if ( reNotASCII.test(desHostname) ) {
desHostname = toASCII(desHostname);
}
// https://github.com/chrisaljoudi/uBlock/issues/1082
// Discard rules with invalid hostnames
if ( (srcHostname !== '*' && reBadHostname.test(srcHostname)) ||
(desHostname !== '*' && reBadHostname.test(desHostname))
) {
continue;
}
type = fields[2];
if ( typeBitOffsets.hasOwnProperty(type) === false ) {
continue;
}
// https://github.com/chrisaljoudi/uBlock/issues/840
// Discard invalid rules
if ( desHostname !== '*' && type !== '*' ) {
continue;
}
action = nameToActionMap[fields[3]];
if ( typeof action !== 'number' || action < 0 || action > 3 ) {
continue;
}
this.setCell(srcHostname, desHostname, type, action);
this.addFromRuleParts(lineIter.next().trim().split(/\s+/));
}
};
/******************************************************************************/
Matrix.prototype.validateRuleParts = function(parts) {
if ( parts.length < 4 ) { return; }
// Ignore hostname-based switch rules
if ( parts[0].endsWith(':') ) { return; }
// Ignore URL-based rules
if ( parts[1].indexOf('/') !== -1 ) { return; }
if ( typeBitOffsets.hasOwnProperty(parts[2]) === false ) { return; }
if ( nameToActionMap.hasOwnProperty(parts[3]) === false ) { return; }
// https://github.com/chrisaljoudi/uBlock/issues/840
// Discard invalid rules
if ( parts[1] !== '*' && parts[2] !== '*' ) { return; }
// Performance: avoid punycoding if hostnames are made only of ASCII chars.
if ( reNotASCII.test(parts[0]) ) { parts[0] = punycode.toASCII(parts[0]); }
if ( reNotASCII.test(parts[1]) ) { parts[1] = punycode.toASCII(parts[1]); }
// https://github.com/chrisaljoudi/uBlock/issues/1082
// Discard rules with invalid hostnames
if (
(parts[0] !== '*' && reBadHostname.test(parts[0])) ||
(parts[1] !== '*' && reBadHostname.test(parts[1]))
) {
return;
}
return parts;
};
/******************************************************************************/
Matrix.prototype.addFromRuleParts = function(parts) {
if ( this.validateRuleParts(parts) !== undefined ) {
this.setCell(parts[0], parts[1], parts[2], nameToActionMap[parts[3]]);
return true;
}
return false;
};
Matrix.prototype.removeFromRuleParts = function(parts) {
if ( this.validateRuleParts(parts) !== undefined ) {
this.setCell(parts[0], parts[1], parts[2], 0);
return true;
}
return false;
};
/******************************************************************************/
var magicId = 1;
Matrix.prototype.toSelfie = function() {
return {
magicId: magicId,
rules: this.rules
rules: Array.from(this.rules)
};
};
/******************************************************************************/
Matrix.prototype.fromSelfie = function(selfie) {
this.rules = selfie.rules;
if ( selfie.magicId !== magicId ) { return false; }
this.rules = new Map(selfie.rules);
this.changed = true;
return true;
};
/******************************************************************************/

View File

@ -1,7 +1,7 @@
/*******************************************************************************
uBlock Origin - a Chromium browser extension to black/white list requests.
Copyright (C) 2015-2017 Raymond Hill
Copyright (C) 2015-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
@ -62,6 +62,7 @@ var nameToSwitchStateMap = {
// For performance purpose, as simple tests as possible
var reHostnameVeryCoarse = /[g-z_-]/;
var reIPv4VeryCoarse = /\.\d+$/;
var reNotASCII = /[^\x20-\x7F]/;
// http://tools.ietf.org/html/rfc5952
// 4.3: "MUST be represented in lowercase"
@ -96,10 +97,11 @@ var selectHostnameBroadener = function(hostname) {
/******************************************************************************/
HnSwitches.prototype.reset = function() {
this.switches = {};
this.switches = new Map();
this.n = '';
this.z = '';
this.r = 0;
this.changed = true;
};
/******************************************************************************/
@ -114,14 +116,15 @@ HnSwitches.prototype.toggle = function(switchName, hostname, newVal) {
if ( newVal === this.evaluate(switchName, hostname) ) {
return false;
}
var bits = this.switches[hostname] || 0;
var bits = this.switches.get(hostname) || 0;
bits &= ~(3 << bitOffset);
bits |= newVal << bitOffset;
if ( bits === 0 ) {
delete this.switches[hostname];
this.switches.delete(hostname);
} else {
this.switches[hostname] = bits;
this.switches.set(hostname, bits);
}
this.changed = true;
return true;
};
@ -139,33 +142,30 @@ HnSwitches.prototype.toggleOneZ = function(switchName, hostname, newState) {
if ( newState === undefined ) {
newState = !state;
}
var bits = this.switches[hostname] || 0;
var bits = this.switches.get(hostname) || 0;
bits &= ~(3 << bitOffset);
if ( bits === 0 ) {
delete this.switches[hostname];
this.switches.delete(hostname);
} else {
this.switches[hostname] = bits;
this.switches.set(hostname, bits);
}
state = this.evaluateZ(switchName, hostname);
if ( state === newState ) {
return true;
if ( state !== newState ) {
this.switches.set(hostname, bits | ((newState ? 1 : 2) << bitOffset));
}
this.switches[hostname] = bits | ((newState ? 1 : 2) << bitOffset);
this.changed = true;
return true;
};
/******************************************************************************/
HnSwitches.prototype.toggleBranchZ = function(switchName, targetHostname, newState) {
var changed = this.toggleOneZ(switchName, targetHostname, newState);
var targetLen = targetHostname.length;
this.toggleOneZ(switchName, targetHostname, newState);
// Turn off all descendant switches, they will inherit the state of the
// branch's origin.
for ( var hostname in this.switches ) {
if ( this.switches.hasOwnProperty(hostname) === false ) {
continue;
}
var targetLen = targetHostname.length;
for ( var hostname of this.switches.keys() ) {
if ( hostname === targetHostname ) {
continue;
}
@ -178,10 +178,10 @@ HnSwitches.prototype.toggleBranchZ = function(switchName, targetHostname, newSta
if ( hostname.charAt(hostname.length - targetLen - 1) !== '.' ) {
continue;
}
changed = this.toggle(switchName, hostname, 0) || changed;
this.toggle(switchName, hostname, 0);
}
return changed;
return this.changed;
};
/******************************************************************************/
@ -200,8 +200,8 @@ HnSwitches.prototype.toggleZ = function(switchName, hostname, deep, newState) {
// 2 = forced default state (to override a broader non-default state)
HnSwitches.prototype.evaluate = function(switchName, hostname) {
var bits = this.switches[hostname] || 0;
if ( bits === 0 ) {
var bits = this.switches.get(hostname);
if ( bits === undefined ) {
return 0;
}
var bitOffset = switchBitOffsets[switchName];
@ -224,8 +224,8 @@ HnSwitches.prototype.evaluateZ = function(switchName, hostname) {
hn = hostname,
broadenSource = selectHostnameBroadener(hn);
for (;;) {
bits = this.switches[hn] || 0;
if ( bits !== 0 ) {
bits = this.switches.get(hn);
if ( bits !== undefined ) {
bits = bits >>> bitOffset & 3;
if ( bits !== 0 ) {
this.z = hn;
@ -252,20 +252,15 @@ HnSwitches.prototype.toLogData = function() {
/******************************************************************************/
HnSwitches.prototype.toString = function() {
HnSwitches.prototype.toArray = function() {
var out = [],
switchName, val,
hostname,
toUnicode = punycode.toUnicode;
for ( hostname in this.switches ) {
if ( this.switches.hasOwnProperty(hostname) === false ) {
continue;
}
for ( switchName in switchBitOffsets ) {
for ( var hostname of this.switches.keys() ) {
for ( var switchName in switchBitOffsets ) {
if ( switchBitOffsets.hasOwnProperty(switchName) === false ) {
continue;
}
val = this.evaluate(switchName, hostname);
var val = this.evaluate(switchName, hostname);
if ( val === 0 ) { continue; }
if ( hostname.indexOf('xn--') !== -1 ) {
hostname = toUnicode(hostname);
@ -273,63 +268,57 @@ HnSwitches.prototype.toString = function() {
out.push(switchName + ': ' + hostname + ' ' + switchStateToNameMap[val]);
}
}
return out.join('\n');
return out;
};
HnSwitches.prototype.toString = function() {
return this.toArray().join('\n');
};
/******************************************************************************/
HnSwitches.prototype.fromString = function(text) {
var lineIter = new µBlock.LineIterator(text),
line, pos, fields,
switchName, hostname, state,
reNotASCII = /[^\x20-\x7F]/,
toASCII = punycode.toASCII;
var lineIter = new µBlock.LineIterator(text);
this.reset();
while ( lineIter.eot() === false ) {
line = lineIter.next().trim();
pos = line.indexOf('# ');
if ( pos !== -1 ) {
line = line.slice(0, pos).trim();
}
if ( line === '' ) {
continue;
}
fields = line.split(/\s+/);
if ( fields.length !== 3 ) {
continue;
}
switchName = fields[0];
pos = switchName.indexOf(':');
if ( pos === -1 ) {
continue;
}
switchName = switchName.slice(0, pos);
if ( switchBitOffsets.hasOwnProperty(switchName) === false ) {
continue;
}
// Performance: avoid punycoding if hostname is made only of
// ASCII characters.
hostname = fields[1];
if ( reNotASCII.test(hostname) ) {
hostname = toASCII(hostname);
}
state = fields[2];
if ( nameToSwitchStateMap.hasOwnProperty(state) === false ) {
continue;
}
this.toggle(switchName, hostname, nameToSwitchStateMap[state]);
this.addFromRuleParts(lineIter.next().trim().split(/\s+/));
}
};
/******************************************************************************/
HnSwitches.prototype.validateRuleParts = function(parts) {
if ( parts.length < 3 ) { return; }
if ( parts[0].endsWith(':') === false ) { return; }
if ( nameToSwitchStateMap.hasOwnProperty(parts[2]) === false ) { return; }
// Performance: avoid punycoding if hostname is made only of ASCII chars.
if ( reNotASCII.test(parts[1]) ) { parts[1] = punycode.toASCII(parts[1]); }
return parts;
};
/******************************************************************************/
HnSwitches.prototype.addFromRuleParts = function(parts) {
if ( this.validateRuleParts(parts) !== undefined ) {
var switchName = parts[0].slice(0, -1);
if ( switchBitOffsets.hasOwnProperty(switchName) ) {
this.toggle(switchName, parts[1], nameToSwitchStateMap[parts[2]]);
return true;
}
}
return false;
};
HnSwitches.prototype.removeFromRuleParts = function(parts) {
if ( this.validateRuleParts(parts) !== undefined ) {
this.toggle(parts[0].slice(0, -1), parts[1], 0);
return true;
}
return false;
};
/******************************************************************************/
return HnSwitches;
/******************************************************************************/

View File

@ -863,47 +863,57 @@ var getLists = function(callback) {
var getRules = function() {
return {
permanentRules: µb.permanentFirewall.toString() + '\n' + µb.permanentURLFiltering.toString(),
sessionRules: µb.sessionFirewall.toString() + '\n' + µb.sessionURLFiltering.toString(),
hnSwitches: µb.hnSwitches.toString()
permanentRules: µb.permanentFirewall.toArray().concat(
µb.permanentURLFiltering.toArray()
),
sessionRules: µb.sessionFirewall.toArray().concat(
µb.sessionURLFiltering.toArray()
),
hnSwitches: µb.hnSwitches.toArray()
};
};
// Untangle firewall rules, url rules and switches.
var untangleRules = function(s) {
var textEnd = s.length;
var lineBeg = 0, lineEnd;
var line;
var firewallRules = [];
var urlRules = [];
var switches = [];
var reIsSwitchRule = /^[a-z-]+:\s/;
while ( lineBeg < textEnd ) {
lineEnd = s.indexOf('\n', lineBeg);
if ( lineEnd < 0 ) {
lineEnd = s.indexOf('\r', lineBeg);
if ( lineEnd < 0 ) {
lineEnd = textEnd;
}
var modifyRuleset = function(details) {
var swRuleset = µb.hnSwitches,
hnRuleset, urlRuleset;
if ( details.permanent ) {
hnRuleset = µb.permanentFirewall;
urlRuleset = µb.permanentURLFiltering;
} else {
hnRuleset = µb.sessionFirewall;
urlRuleset = µb.sessionURLFiltering;
}
var toRemove = new Set(details.toRemove.trim().split(/\s*[\n\r]+\s*/));
var rule, parts, _;
for ( rule of toRemove ) {
if ( rule === '' ) { continue; }
parts = rule.split(/\s/);
_ = hnRuleset.removeFromRuleParts(parts) ||
swRuleset.removeFromRuleParts(parts) ||
urlRuleset.removeFromRuleParts(parts);
}
var toAdd = new Set(details.toAdd.trim().split(/\s*[\n\r]+\s*/));
for ( rule of toAdd ) {
if ( rule === '' ) { continue; }
parts = rule.split(/\s/);
_ = hnRuleset.addFromRuleParts(parts) ||
swRuleset.addFromRuleParts(parts) ||
urlRuleset.addFromRuleParts(parts);
}
if ( details.permanent ) {
if ( hnRuleset.changed ) {
µb.savePermanentFirewallRules();
hnRuleset.changed = false;
}
line = s.slice(lineBeg, lineEnd).trim();
lineBeg = lineEnd + 1;
if ( reIsSwitchRule.test(line) ) {
switches.push(line);
} else if ( line.indexOf('://') !== -1 ) {
urlRules.push(line);
} else {
firewallRules.push(line);
if ( urlRuleset.changed ) {
µb.savePermanentURLFilteringRules();
urlRuleset.changed = false;
}
}
return {
firewallRules: firewallRules.join('\n'),
urlRules: urlRules.join('\n'),
switches: switches.join('\n')
};
if ( swRuleset.changed ) {
µb.saveHostnameSwitches();
swRuleset.changed = false;
}
};
/******************************************************************************/
@ -938,6 +948,13 @@ var onMessage = function(request, sender, callback) {
response = getRules();
break;
case 'modifyRuleset':
// https://github.com/chrisaljoudi/uBlock/issues/772
µb.cosmeticFilteringEngine.removeFromSelectorCache('*');
modifyRuleset(request);
response = getRules();
break;
case 'purgeAllCaches':
if ( request.hard ) {
µb.assets.remove(/./);
@ -968,28 +985,6 @@ var onMessage = function(request, sender, callback) {
resetUserData();
break;
case 'setSessionRules':
// https://github.com/chrisaljoudi/uBlock/issues/772
µb.cosmeticFilteringEngine.removeFromSelectorCache('*');
response = untangleRules(request.rules);
µb.sessionFirewall.fromString(response.firewallRules);
µb.sessionURLFiltering.fromString(response.urlRules);
µb.hnSwitches.fromString(response.switches);
µb.saveHostnameSwitches();
response = getRules();
break;
case 'setPermanentRules':
response = untangleRules(request.rules);
µb.permanentFirewall.fromString(response.firewallRules);
µb.savePermanentFirewallRules();
µb.permanentURLFiltering.fromString(response.urlRules);
µb.savePermanentURLFilteringRules();
µb.hnSwitches.fromString(response.switches);
µb.saveHostnameSwitches();
response = getRules();
break;
case 'validateWhitelistString':
response = µb.validateWhitelistString(request.raw);
break;

View File

@ -1,7 +1,7 @@
/*******************************************************************************
uBlock Origin - a browser extension to black/white list requests.
Copyright (C) 2015-2017 Raymond Hill
Copyright (C) 2015-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
@ -136,23 +136,23 @@ URLNetFiltering.prototype.reset = function() {
this.url = '';
this.type = '';
this.r = 0;
this.changed = false;
};
/******************************************************************************/
URLNetFiltering.prototype.assign = function(other) {
var thisRules = this.rules,
otherRules = other.rules;
// Remove rules not in other
for ( var key of thisRules.keys() ) {
if ( otherRules.has(key) === false ) {
thisRules.delete(key);
for ( var key of this.rules.keys() ) {
if ( other.rules.has(key) === false ) {
this.rules.delete(key);
}
}
// Add/change rules in other
for ( var entry of otherRules ) {
thisRules.set(entry[0], entry[1].slice());
for ( var entry of other.rules ) {
this.rules.set(entry[0], entry[1].slice());
}
this.changed = true;
};
/******************************************************************************/
@ -176,6 +176,7 @@ URLNetFiltering.prototype.setRule = function(srcHostname, url, type, action) {
} else {
addRuleEntry(entries, url, action);
}
this.changed = true;
return true;
};
@ -195,6 +196,7 @@ URLNetFiltering.prototype.removeRule = function(srcHostname, url, type) {
if ( entries.length === 0 ) {
this.rules.delete(bucketKey);
}
this.changed = true;
return true;
};
@ -276,7 +278,6 @@ URLNetFiltering.prototype.intToActionMap = new Map([
/******************************************************************************/
URLNetFiltering.prototype.copyRules = function(other, context, urls, type) {
var changed = false;
var url, otherOwn, thisOwn;
var i = urls.length;
while ( i-- ) {
@ -287,21 +288,21 @@ URLNetFiltering.prototype.copyRules = function(other, context, urls, type) {
thisOwn = this.r !== 0 && this.context === context && this.url === url && this.type === type;
if ( otherOwn && !thisOwn ) {
this.setRule(context, url, type, other.r);
changed = true;
this.changed = true;
}
if ( !otherOwn && thisOwn ) {
this.removeRule(context, url, type);
changed = true;
this.changed = true;
}
}
return changed;
return this.changed;
};
/******************************************************************************/
// "url-filtering:" hostname url type action
URLNetFiltering.prototype.toString = function() {
URLNetFiltering.prototype.toArray = function() {
var out = [],
key, pos, hn, type, entries, i, entry;
for ( var item of this.rules ) {
@ -321,40 +322,52 @@ URLNetFiltering.prototype.toString = function() {
);
}
}
return out.sort().join('\n');
return out;
};
URLNetFiltering.prototype.toString = function() {
return this.toArray().sort().join('\n');
};
/******************************************************************************/
URLNetFiltering.prototype.fromString = function(text) {
this.reset();
var lineIter = new µBlock.LineIterator(text),
line, fields;
var lineIter = new µBlock.LineIterator(text);
while ( lineIter.eot() === false ) {
line = lineIter.next().trim();
if ( line === '' ) { continue; }
// Coarse test
if ( line.indexOf('://') === -1 ) {
continue;
}
fields = line.split(/\s+/);
if ( fields.length !== 4 ) {
continue;
}
// Finer test
if ( fields[1].indexOf('://') === -1 ) {
continue;
}
if ( nameToActionMap.hasOwnProperty(fields[3]) === false ) {
continue;
}
this.setRule(fields[0], fields[1], fields[2], nameToActionMap[fields[3]]);
this.addFromRuleParts(lineIter.next().trim().split(/\s+/));
}
};
/******************************************************************************/
URLNetFiltering.prototype.validateRuleParts = function(parts) {
if ( parts.length !== 4 ) { return; }
if ( parts[1].indexOf('://') === -1 ) { return; }
if ( nameToActionMap.hasOwnProperty(parts[3]) === false ) { return; }
return parts;
};
/******************************************************************************/
URLNetFiltering.prototype.addFromRuleParts = function(parts) {
if ( this.validateRuleParts(parts) !== undefined ) {
this.setRule(parts[0], parts[1], parts[2], nameToActionMap[parts[3]]);
return true;
}
return false;
};
URLNetFiltering.prototype.removeFromRuleParts = function(parts) {
if ( this.validateRuleParts(parts) !== undefined ) {
this.removeRule(parts[0], parts[1], parts[2]);
return true;
}
return false;
};
/******************************************************************************/
return URLNetFiltering;
/******************************************************************************/