mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-02 17:02:40 +01:00
make use of CodeMirror in "My rules" pane
This commit is contained in:
parent
caef7d00bb
commit
b10ac0020d
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1,38 +1,40 @@
|
||||
div > p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
div > p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
code {
|
||||
background-color: #eee;
|
||||
font: 11px monospace;
|
||||
padding: 2px 4px;
|
||||
body {
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
#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;
|
||||
}
|
||||
|
@ -4,52 +4,50 @@
|
||||
<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"></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>
|
||||
<!-- TO BE IMPLEMENTED: <div class="ruleFilter"><span class="fa"></span> <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> </li>
|
||||
</ul>
|
||||
</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>
|
||||
<script src="js/vapi-client.js"></script>
|
||||
|
@ -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');
|
||||
}
|
||||
ulLeft.append(liLeft);
|
||||
ulRight.append(liRight);
|
||||
}
|
||||
/******************************************************************************/
|
||||
|
||||
uDom('#diff > .left > .rulesContainer').append(ulLeft);
|
||||
uDom('#diff > .right > .rulesContainer').append(ulRight);
|
||||
uDom('#diff').toggleClass('dirty', details.sessionRules !== details.permanentRules);
|
||||
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');
|
||||
}
|
||||
cleanToken = mergeView.editor().changeGeneration();
|
||||
onChange(true);
|
||||
};
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
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(); });
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -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,59 +268,53 @@ 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;
|
||||
this.addFromRuleParts(lineIter.next().trim().split(/\s+/));
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
};
|
||||
|
||||
// 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;
|
||||
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;
|
||||
};
|
||||
|
||||
this.toggle(switchName, hostname, nameToSwitchStateMap[state]);
|
||||
HnSwitches.prototype.removeFromRuleParts = function(parts) {
|
||||
if ( this.validateRuleParts(parts) !== undefined ) {
|
||||
this.toggle(parts[0].slice(0, -1), parts[1], 0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
line = s.slice(lineBeg, lineEnd).trim();
|
||||
lineBeg = lineEnd + 1;
|
||||
|
||||
if ( reIsSwitchRule.test(line) ) {
|
||||
switches.push(line);
|
||||
} else if ( line.indexOf('://') !== -1 ) {
|
||||
urlRules.push(line);
|
||||
var modifyRuleset = function(details) {
|
||||
var swRuleset = µb.hnSwitches,
|
||||
hnRuleset, urlRuleset;
|
||||
if ( details.permanent ) {
|
||||
hnRuleset = µb.permanentFirewall;
|
||||
urlRuleset = µb.permanentURLFiltering;
|
||||
} else {
|
||||
firewallRules.push(line);
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
@ -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,36 +322,48 @@ 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;
|
||||
this.addFromRuleParts(lineIter.next().trim().split(/\s+/));
|
||||
}
|
||||
fields = line.split(/\s+/);
|
||||
if ( fields.length !== 4 ) {
|
||||
continue;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
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;
|
||||
}
|
||||
// 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]]);
|
||||
return false;
|
||||
};
|
||||
|
||||
URLNetFiltering.prototype.removeFromRuleParts = function(parts) {
|
||||
if ( this.validateRuleParts(parts) !== undefined ) {
|
||||
this.removeRule(parts[0], parts[1], parts[2]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
Loading…
Reference in New Issue
Block a user