Skip to content

Commit 59c9a34

Browse files
committed
Add ability to quickly create exceptions in logger
This is a feature under development, hidden behind a new advanced setting, `filterAuthorMode` which default to `false`. Ability to point-and-click to create temporary exception filters for static extended filters (i.e. cosmetic, scriptlet & html filters) from within the summary pane in the logger. The button to toggle on/off temporary exception filter is labeled `#@#`. The created exceptions are temporary and will be lost when restarting uBO, or manually toggling off the exception filters. Creating temporary exception filters does not cause the filter lists to reloaded, and thus there is no overhead in creating/removing these temporary exception filters.
1 parent 733b233 commit 59c9a34

File tree

9 files changed

+203
-47
lines changed

9 files changed

+203
-47
lines changed

src/css/logger-ui.css

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ body[dir="rtl"] #netFilteringDialog > .panes > .details > div > span:nth-of-type
660660
border-left: 1px solid white;
661661
}
662662
#netFilteringDialog > .panes > .details > div > span:nth-of-type(2) {
663+
flex-grow: 1;
663664
max-height: 20vh;
664665
overflow: hidden auto;
665666
white-space: pre-line
@@ -675,6 +676,26 @@ body[dir="rtl"] #netFilteringDialog > .panes > .details > div > span:nth-of-type
675676
#netFilteringDialog > .panes > .details > div > span:nth-of-type(2) .fa-icon:hover {
676677
opacity: 1;
677678
}
679+
#netFilteringDialog > .panes > .details .exceptor {
680+
align-items: center;
681+
border-left: 1px solid white;
682+
cursor: pointer;
683+
display: inline-flex;
684+
font-family: monospace;
685+
opacity: 0.8;
686+
}
687+
#netFilteringDialog > .panes > .details .exceptor:hover {
688+
opacity: 1;
689+
}
690+
#netFilteringDialog > .panes > .details .exceptored .filter {
691+
text-decoration: line-through;
692+
}
693+
#netFilteringDialog > .panes > .details .exceptored .exceptor {
694+
background-color: lightblue;
695+
}
696+
#netFilteringDialog > .panes > .details .exceptor::before {
697+
content: '#@#';
698+
}
678699
#netFilteringDialog > div.panes > .dynamic > .toolbar {
679700
padding-bottom: 1em;
680701
}

src/js/background.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const µBlock = (( ) => { // jshint ignore:line
5353
extensionUpdateForceReload: false,
5454
ignoreRedirectFilters: false,
5555
ignoreScriptInjectFilters: false,
56+
filterAuthorMode: false,
5657
loggerPopupType: 'popup',
5758
manualUpdateAssetFetchPeriod: 500,
5859
popupFontSize: 'unset',

src/js/cosmetic-filtering.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,12 @@ FilterContainer.prototype.randomAlphaToken = function() {
826826

827827
/******************************************************************************/
828828

829+
FilterContainer.prototype.getSession = function() {
830+
return this.specificFilters.session;
831+
};
832+
833+
/******************************************************************************/
834+
829835
FilterContainer.prototype.retrieveGenericSelectors = function(request) {
830836
if ( this.acceptedCount === 0 ) { return; }
831837
if ( !request.ids && !request.classes ) { return; }
@@ -990,20 +996,23 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
990996
}
991997
}
992998

999+
// Retrieve temporary filters
1000+
this.specificFilters.session.retrieve([ dummySet, exceptionSet ]);
1001+
9931002
// Retrieve filters with a non-empty hostname
9941003
this.specificFilters.retrieve(
9951004
hostname,
9961005
options.noSpecificCosmeticFiltering !== true
9971006
? [ specificSet, exceptionSet, proceduralSet, exceptionSet ]
998-
: [ dummySet, exceptionSet, dummySet, exceptionSet ],
1007+
: [ dummySet, exceptionSet ],
9991008
1
10001009
);
10011010
// Retrieve filters with an empty hostname
10021011
this.specificFilters.retrieve(
10031012
hostname,
10041013
options.noGenericCosmeticFiltering !== true
10051014
? [ specificSet, exceptionSet, proceduralSet, exceptionSet ]
1006-
: [ dummySet, exceptionSet, dummySet, exceptionSet ],
1015+
: [ dummySet, exceptionSet ],
10071016
2
10081017
);
10091018
// Retrieve filters with a non-empty entity
@@ -1012,7 +1021,7 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
10121021
`${hostname.slice(0, -request.domain.length)}${request.entity}`,
10131022
options.noSpecificCosmeticFiltering !== true
10141023
? [ specificSet, exceptionSet, proceduralSet, exceptionSet ]
1015-
: [ dummySet, exceptionSet, dummySet, exceptionSet ],
1024+
: [ dummySet, exceptionSet ],
10161025
1
10171026
);
10181027
}

src/js/html-filtering.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,10 @@
334334
}
335335
};
336336

337+
api.getSession = function() {
338+
return filterDB.session;
339+
};
340+
337341
api.retrieve = function(details) {
338342
const hostname = details.hostname;
339343

@@ -350,6 +354,7 @@
350354
const procedurals = new Set();
351355
const exceptions = new Set();
352356

357+
filterDB.session.retrieve([ new Set(), exceptions ]);
353358
filterDB.retrieve(
354359
hostname,
355360
[ plains, exceptions, procedurals, exceptions ]

src/js/logger-ui.js

Lines changed: 74 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ let filteredLoggerEntryVoidedCount = 0;
4141
let popupLoggerBox;
4242
let popupLoggerTooltips;
4343
let activeTabId = 0;
44+
let filterAuthorMode = false;
4445
let selectedTabId = 0;
4546
let netInspectorPaused = false;
4647

@@ -64,7 +65,7 @@ const tabIdFromAttribute = function(elem) {
6465

6566
// Current design allows for only one modal DOM-based dialog at any given time.
6667
//
67-
const modalDialog = (function() {
68+
const modalDialog = (( ) => {
6869
const overlay = uDom.nodeFromId('modalOverlay');
6970
const container = overlay.querySelector(
7071
':scope > div > div:nth-of-type(1)'
@@ -949,6 +950,8 @@ const onLogBufferRead = function(response) {
949950
allTabIdsToken = response.tabIdsToken;
950951
}
951952

953+
filterAuthorMode = response.filterAuthorMode === true;
954+
952955
if ( activeTabIdChanged ) {
953956
pageSelectorFromURLHash();
954957
}
@@ -1085,7 +1088,7 @@ const reloadTab = function(ev) {
10851088
/******************************************************************************/
10861089
/******************************************************************************/
10871090

1088-
(function() {
1091+
(( ) => {
10891092
const reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/;
10901093
const reSchemeOnly = /^[\w-]+:$/;
10911094
const staticFilterTypes = {
@@ -1203,24 +1206,35 @@ const reloadTab = function(ev) {
12031206
);
12041207
};
12051208

1206-
const onClick = function(ev) {
1209+
const onClick = async function(ev) {
12071210
const target = ev.target;
12081211
const tcl = target.classList;
12091212

12101213
// Select a mode
12111214
if ( tcl.contains('header') ) {
1212-
dialog.setAttribute('data-pane', target.getAttribute('data-pane') );
12131215
ev.stopPropagation();
1216+
dialog.setAttribute('data-pane', target.getAttribute('data-pane') );
12141217
return;
12151218
}
12161219

1220+
// Toggle temporary exception filter
1221+
if ( tcl.contains('exceptor') ) {
1222+
ev.stopPropagation();
1223+
const status = await messaging.send('loggerUI', {
1224+
what: 'toggleTemporaryException',
1225+
filter: filterFromTargetRow(),
1226+
});
1227+
const row = target.closest('div');
1228+
row.classList.toggle('exceptored', status);
1229+
return;
1230+
}
1231+
12171232
// Create static filter
12181233
if ( target.id === 'createStaticFilter' ) {
1234+
ev.stopPropagation();
12191235
const value = staticFilterNode().value;
12201236
// Avoid duplicates
1221-
if ( createdStaticFilters.hasOwnProperty(value) ) {
1222-
return;
1223-
}
1237+
if ( createdStaticFilters.hasOwnProperty(value) ) { return; }
12241238
createdStaticFilters[value] = true;
12251239
if ( value !== '' ) {
12261240
messaging.send('loggerUI', {
@@ -1232,109 +1246,103 @@ const reloadTab = function(ev) {
12321246
});
12331247
}
12341248
updateWidgets();
1235-
ev.stopPropagation();
12361249
return;
12371250
}
12381251

12391252
// Save url filtering rule(s)
12401253
if ( target.id === 'saveRules' ) {
1241-
messaging.send('loggerUI', {
1254+
ev.stopPropagation();
1255+
await messaging.send('loggerUI', {
12421256
what: 'saveURLFilteringRules',
12431257
context: selectValue('select.dynamic.origin'),
12441258
urls: targetURLs,
12451259
type: uglyTypeFromSelector('dynamic'),
1246-
}).then(( ) => {
1247-
colorize();
12481260
});
1249-
ev.stopPropagation();
1261+
colorize();
12501262
return;
12511263
}
12521264

12531265
const persist = !!ev.ctrlKey || !!ev.metaKey;
12541266

12551267
// Remove url filtering rule
12561268
if ( tcl.contains('action') ) {
1257-
messaging.send('loggerUI', {
1269+
ev.stopPropagation();
1270+
await messaging.send('loggerUI', {
12581271
what: 'setURLFilteringRule',
12591272
context: selectValue('select.dynamic.origin'),
12601273
url: target.getAttribute('data-url'),
12611274
type: uglyTypeFromSelector('dynamic'),
12621275
action: 0,
12631276
persist: persist,
1264-
}).then(( ) => {
1265-
colorize();
12661277
});
1267-
ev.stopPropagation();
1278+
colorize();
12681279
return;
12691280
}
12701281

12711282
// add "allow" url filtering rule
12721283
if ( tcl.contains('allow') ) {
1273-
messaging.send('loggerUI', {
1284+
ev.stopPropagation();
1285+
await messaging.send('loggerUI', {
12741286
what: 'setURLFilteringRule',
12751287
context: selectValue('select.dynamic.origin'),
12761288
url: target.parentNode.getAttribute('data-url'),
12771289
type: uglyTypeFromSelector('dynamic'),
12781290
action: 2,
12791291
persist: persist,
1280-
}).then(( ) => {
1281-
colorize();
12821292
});
1283-
ev.stopPropagation();
1293+
colorize();
12841294
return;
12851295
}
12861296

12871297
// add "block" url filtering rule
12881298
if ( tcl.contains('noop') ) {
1289-
messaging.send('loggerUI', {
1299+
ev.stopPropagation();
1300+
await messaging.send('loggerUI', {
12901301
what: 'setURLFilteringRule',
12911302
context: selectValue('select.dynamic.origin'),
12921303
url: target.parentNode.getAttribute('data-url'),
12931304
type: uglyTypeFromSelector('dynamic'),
12941305
action: 3,
12951306
persist: persist,
1296-
}).then(( ) => {
1297-
colorize();
12981307
});
1299-
ev.stopPropagation();
1308+
colorize();
13001309
return;
13011310
}
13021311

13031312
// add "block" url filtering rule
13041313
if ( tcl.contains('block') ) {
1305-
messaging.send('loggerUI', {
1314+
ev.stopPropagation();
1315+
await messaging.send('loggerUI', {
13061316
what: 'setURLFilteringRule',
13071317
context: selectValue('select.dynamic.origin'),
13081318
url: target.parentNode.getAttribute('data-url'),
13091319
type: uglyTypeFromSelector('dynamic'),
13101320
action: 1,
13111321
persist: persist,
1312-
}).then(( ) => {
1313-
colorize();
13141322
});
1315-
ev.stopPropagation();
1323+
colorize();
13161324
return;
13171325
}
13181326

13191327
// Force a reload of the tab
13201328
if ( tcl.contains('reload') ) {
1329+
ev.stopPropagation();
13211330
messaging.send('loggerUI', {
13221331
what: 'reloadTab',
13231332
tabId: targetTabId,
13241333
});
1325-
ev.stopPropagation();
13261334
return;
13271335
}
13281336

13291337
// Hightlight corresponding element in target web page
13301338
if ( tcl.contains('picker') ) {
1339+
ev.stopPropagation();
13311340
messaging.send('loggerUI', {
13321341
what: 'launchElementPicker',
13331342
tabId: targetTabId,
13341343
targetURL: 'img\t' + targetURLs[0],
13351344
select: true,
13361345
});
1337-
ev.stopPropagation();
13381346
return;
13391347
}
13401348
};
@@ -1426,6 +1434,37 @@ const reloadTab = function(ev) {
14261434
return urls;
14271435
};
14281436

1437+
const filterFromTargetRow = function() {
1438+
return targetRow.children[1].textContent;
1439+
};
1440+
1441+
const toSummaryPaneFilterNode = async function(receiver, filter) {
1442+
receiver.children[1].textContent = filter;
1443+
if ( filterAuthorMode !== true ) { return; }
1444+
const match = /#@?#/.exec(filter);
1445+
if ( match === null ) { return; }
1446+
const fragment = document.createDocumentFragment();
1447+
fragment.appendChild(document.createTextNode(match[0]));
1448+
const selector = filter.slice(match.index + match[0].length);
1449+
const span = document.createElement('span');
1450+
span.className = 'filter';
1451+
span.textContent = selector;
1452+
fragment.appendChild(span);
1453+
let isTemporaryException = false;
1454+
if ( match[0] === '#@#' ) {
1455+
isTemporaryException = await messaging.send('loggerUI', {
1456+
what: 'hasTemporaryException',
1457+
filter,
1458+
});
1459+
receiver.classList.toggle('exceptored', isTemporaryException);
1460+
}
1461+
if ( match[0] === '##' || isTemporaryException ) {
1462+
receiver.children[2].style.visibility = '';
1463+
}
1464+
receiver.children[1].textContent = '';
1465+
receiver.children[1].appendChild(fragment);
1466+
};
1467+
14291468
const fillSummaryPaneFilterList = async function(rows) {
14301469
const rawFilter = targetRow.children[1].textContent;
14311470
const compiledFilter = targetRow.getAttribute('data-filter');
@@ -1468,7 +1507,7 @@ const reloadTab = function(ev) {
14681507
bestMatchFilter !== '' &&
14691508
Array.isArray(response[bestMatchFilter])
14701509
) {
1471-
rows[0].children[1].textContent = bestMatchFilter;
1510+
toSummaryPaneFilterNode(rows[0], bestMatchFilter);
14721511
rows[1].children[1].appendChild(nodeFromFilter(
14731512
bestMatchFilter,
14741513
response[bestMatchFilter]
@@ -1499,7 +1538,7 @@ const reloadTab = function(ev) {
14991538
});
15001539
handleResponse(response);
15011540
}
1502-
};
1541+
} ;
15031542

15041543
const fillSummaryPane = function() {
15051544
const rows = dialog.querySelectorAll('.pane.details > div');
@@ -1508,12 +1547,12 @@ const reloadTab = function(ev) {
15081547
const trch = tr.children;
15091548
let text;
15101549
// Filter and context
1511-
text = trch[1].textContent;
1550+
text = filterFromTargetRow();
15121551
if (
15131552
(text !== '') &&
15141553
(trcl.contains('cosmeticRealm') || trcl.contains('networkRealm'))
15151554
) {
1516-
rows[0].children[1].textContent = text;
1555+
toSummaryPaneFilterNode(rows[0], text);
15171556
} else {
15181557
rows[0].style.display = 'none';
15191558
}
@@ -1753,7 +1792,7 @@ const reloadTab = function(ev) {
17531792
fillSummaryPane();
17541793
fillDynamicPane();
17551794
fillStaticPane();
1756-
dialog.addEventListener('click', onClick, true);
1795+
dialog.addEventListener('click', ev => { onClick(ev); }, true);
17571796
dialog.addEventListener('change', onSelectChange, true);
17581797
dialog.addEventListener('input', onInputChange, true);
17591798
modalDialog.show();

0 commit comments

Comments
 (0)