diff --git a/src/about.html b/src/about.html
index cfa4819e4..e929c5542 100644
--- a/src/about.html
+++ b/src/about.html
@@ -24,6 +24,7 @@
Punycode.js by Mathias Bynens
Font Awesome by Dave Gandy
CodeMirror by Marijn Haverbeke
+ An implementation of Myers' diff algorithm by Arpad Borsos
diff --git a/src/css/codemirror.css b/src/css/codemirror.css
index 772a91140..4a53399a2 100644
--- a/src/css/codemirror.css
+++ b/src/css/codemirror.css
@@ -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;
+ }
diff --git a/src/css/dyna-rules.css b/src/css/dyna-rules.css
index 033faf9ea..4f0f7207b 100644
--- a/src/css/dyna-rules.css
+++ b/src/css/dyna-rules.css
@@ -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
.
- 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;
}
diff --git a/src/dyna-rules.html b/src/dyna-rules.html
index 66eb7b7c9..b9e156ce3 100644
--- a/src/dyna-rules.html
+++ b/src/dyna-rules.html
@@ -4,51 +4,49 @@
uBlock — Dynamic filtering rules
+
+
+
+
+
-
-
+
+
+
+
+
+
diff --git a/src/js/dyna-rules.js b/src/js/dyna-rules.js
index 30e1b0224..c598d99b5 100644
--- a/src/js/dyna-rules.js
+++ b/src/js/dyna-rules.js
@@ -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(); });
/******************************************************************************/
diff --git a/src/js/dynamic-net-filtering.js b/src/js/dynamic-net-filtering.js
index 34bad0df4..0e6199907 100644
--- a/src/js/dynamic-net-filtering.js
+++ b/src/js/dynamic-net-filtering.js
@@ -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;
};
/******************************************************************************/
diff --git a/src/js/hnswitches.js b/src/js/hnswitches.js
index 17954c961..c47e18144 100644
--- a/src/js/hnswitches.js
+++ b/src/js/hnswitches.js
@@ -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;
/******************************************************************************/
diff --git a/src/js/messaging.js b/src/js/messaging.js
index 8cefbfc40..fd1c96568 100644
--- a/src/js/messaging.js
+++ b/src/js/messaging.js
@@ -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;
diff --git a/src/js/url-net-filtering.js b/src/js/url-net-filtering.js
index b317c9200..7ba9c56ec 100644
--- a/src/js/url-net-filtering.js
+++ b/src/js/url-net-filtering.js
@@ -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;
/******************************************************************************/