Skip to content

Commit

Permalink
Rewrite logger's "exceptor" feature
Browse files Browse the repository at this point in the history
Related issue:
- uBlockOrigin/uBlock-issues#1861

The "exceptor" feature has been rewritten, with the following
changes as a result:

- The excepted filters cease to exist when closing the logger
- It's now possible to temporary except network filters

When toggling on/off a temporary exception, filter lists are now
fully reloaded. This simplified managing temporary exceptions, and
made it easy to implement temporary exception for network filters,
but this also means there might be a perceptible delay when
adding/removing temporary exceptions. At this point I consider
this an acceptable side-effect just to bring the ability to easily
create temporary exception for network filters, while this
simplified the existing temporary exception code throughout.
  • Loading branch information
gorhill committed Dec 14, 2022
1 parent ce3f852 commit a91781a
Show file tree
Hide file tree
Showing 13 changed files with 127 additions and 255 deletions.
3 changes: 3 additions & 0 deletions platform/common/vapi-background.js
Expand Up @@ -919,6 +919,9 @@ vAPI.messaging = {
this.onPortDisconnect(port);
}
}
if ( this.defaultHandler ) {
this.defaultHandler(message, null, ( ) => { });
}
},

onFrameworkMessage: function(request, port, callback) {
Expand Down
3 changes: 3 additions & 0 deletions src/css/logger-ui.css
Expand Up @@ -678,6 +678,9 @@ body[dir="rtl"] #netFilteringDialog > .panes > .details > div > span:nth-of-type
background-color: rgb(var(--primary-50) / 50%);
}
#netFilteringDialog > .panes > .details .exceptor::before {
content: '@@';
}
#netFilteringDialog.extendedRealm > .panes > .details .exceptor::before {
content: '#@#';
}
#netFilteringDialog > div.panes > .dynamic > .toolbar {
Expand Down
3 changes: 3 additions & 0 deletions src/js/background.js
Expand Up @@ -211,6 +211,9 @@ const µBlock = { // jshint ignore:line
availableFilterLists: {},
badLists: new Map(),

inMemoryFilters: [],
inMemoryFiltersCompiled: '',

// https://github.com/uBlockOrigin/uBlock-issues/issues/974
// This can be used to defer filtering decision-making.
readyToFilter: false,
Expand Down
30 changes: 2 additions & 28 deletions src/js/cosmetic-filtering.js
Expand Up @@ -27,10 +27,7 @@ import './utils.js';
import logger from './logger.js';
import µb from './background.js';

import {
StaticExtFilteringHostnameDB,
StaticExtFilteringSessionDB,
} from './static-ext-filtering-db.js';
import { StaticExtFilteringHostnameDB } from './static-ext-filtering-db.js';

/******************************************************************************/
/******************************************************************************/
Expand Down Expand Up @@ -236,10 +233,7 @@ const FilterContainer = function() {
// specific filters
this.specificFilters = new StaticExtFilteringHostnameDB(2);

// temporary filters
this.sessionFilterDB = new StaticExtFilteringSessionDB();

// low generic cosmetic filters: map of hash => array of selectors
// low generic cosmetic filters: map of hash => stringified selector list
this.lowlyGeneric = new Map();

// highly generic selectors sets
Expand Down Expand Up @@ -478,15 +472,6 @@ FilterContainer.prototype.compileSpecificSelector = function(

/******************************************************************************/

FilterContainer.prototype.compileTemporary = function(parser) {
return {
session: this.sessionFilterDB,
selector: parser.result.compiled,
};
};

/******************************************************************************/

FilterContainer.prototype.fromCompiledContent = function(reader, options) {
if ( options.skipCosmetic ) {
this.skipCompiledContent(reader, 'SPECIFIC');
Expand Down Expand Up @@ -697,12 +682,6 @@ FilterContainer.prototype.disableSurveyor = function(details) {

/******************************************************************************/

FilterContainer.prototype.getSession = function() {
return this.sessionFilterDB;
};

/******************************************************************************/

FilterContainer.prototype.cssRuleFromProcedural = function(json) {
const pfilter = JSON.parse(json);
if ( pfilter.cssable !== true ) { return; }
Expand Down Expand Up @@ -831,11 +810,6 @@ FilterContainer.prototype.retrieveSpecificSelectors = function(
}
}

// Retrieve temporary filters
if ( this.sessionFilterDB.isNotEmpty ) {
this.sessionFilterDB.retrieve([ null, exceptionSet ]);
}

// Retrieve filters with a non-empty hostname
this.specificFilters.retrieve(
hostname,
Expand Down
20 changes: 1 addition & 19 deletions src/js/html-filtering.js
Expand Up @@ -27,18 +27,14 @@ import logger from './logger.js';
import µb from './background.js';
import { sessionFirewall } from './filtering-engines.js';

import {
StaticExtFilteringHostnameDB,
StaticExtFilteringSessionDB,
} from './static-ext-filtering-db.js';
import { StaticExtFilteringHostnameDB } from './static-ext-filtering-db.js';

/******************************************************************************/

const pselectors = new Map();
const duplicates = new Set();

const filterDB = new StaticExtFilteringHostnameDB(2);
const sessionFilterDB = new StaticExtFilteringSessionDB();

let acceptedCount = 0;
let discardedCount = 0;
Expand Down Expand Up @@ -347,13 +343,6 @@ htmlFilteringEngine.compile = function(parser, writer) {
}
};

htmlFilteringEngine.compileTemporary = function(parser) {
return {
session: sessionFilterDB,
selector: parser.result.compiled,
};
};

htmlFilteringEngine.fromCompiledContent = function(reader) {
// Don't bother loading filters if stream filtering is not supported.
if ( µb.canFilterResponseData === false ) { return; }
Expand All @@ -373,20 +362,13 @@ htmlFilteringEngine.fromCompiledContent = function(reader) {
}
};

htmlFilteringEngine.getSession = function() {
return sessionFilterDB;
};

htmlFilteringEngine.retrieve = function(details) {
const hostname = details.hostname;

const plains = new Set();
const procedurals = new Set();
const exceptions = new Set();

if ( sessionFilterDB.isNotEmpty ) {
sessionFilterDB.retrieve([ null, exceptions ]);
}
filterDB.retrieve(
hostname,
[ plains, exceptions, procedurals, exceptions ]
Expand Down
20 changes: 1 addition & 19 deletions src/js/httpheader-filtering.js
Expand Up @@ -28,16 +28,12 @@ import µb from './background.js';
import { entityFromDomain } from './uri-utils.js';
import { sessionFirewall } from './filtering-engines.js';

import {
StaticExtFilteringHostnameDB,
StaticExtFilteringSessionDB,
} from './static-ext-filtering-db.js';
import { StaticExtFilteringHostnameDB } from './static-ext-filtering-db.js';

/******************************************************************************/

const duplicates = new Set();
const filterDB = new StaticExtFilteringHostnameDB(1);
const sessionFilterDB = new StaticExtFilteringSessionDB();

const $headers = new Set();
const $exceptions = new Set();
Expand Down Expand Up @@ -123,13 +119,6 @@ httpheaderFilteringEngine.compile = function(parser, writer) {
}
};

httpheaderFilteringEngine.compileTemporary = function(parser) {
return {
session: sessionFilterDB,
selector: parser.result.compiled.slice(15, -1),
};
};

// 01234567890123456789
// responseheader(name)
// ^ ^
Expand All @@ -152,10 +141,6 @@ httpheaderFilteringEngine.fromCompiledContent = function(reader) {
}
};

httpheaderFilteringEngine.getSession = function() {
return sessionFilterDB;
};

httpheaderFilteringEngine.apply = function(fctxt, headers) {
if ( filterDB.size === 0 ) { return; }

Expand All @@ -173,9 +158,6 @@ httpheaderFilteringEngine.apply = function(fctxt, headers) {
$headers.clear();
$exceptions.clear();

if ( sessionFilterDB.isNotEmpty ) {
sessionFilterDB.retrieve([ null, $exceptions ]);
}
filterDB.retrieve(hostname, [ $headers, $exceptions ]);
filterDB.retrieve(entity, [ $headers, $exceptions ], 1);
if ( $headers.size === 0 ) { return; }
Expand Down
32 changes: 13 additions & 19 deletions src/js/logger-ui.js
Expand Up @@ -1246,9 +1246,13 @@ const reloadTab = function(ev) {
// Toggle temporary exception filter
if ( tcl.contains('exceptor') ) {
ev.stopPropagation();
const filter = filterFromTargetRow();
const exceptedFilter = dom.cl.has(targetRow, 'extendedRealm')
? `#@#${filter.replace(/^.*?#@?#/, '')}`
: `@@${filter.replace(/^@@/, '')}`;
const status = await messaging.send('loggerUI', {
what: 'toggleTemporaryException',
filter: filterFromTargetRow(),
what: 'toggleInMemoryFilter',
filter: exceptedFilter,
});
const row = target.closest('div');
dom.cl.toggle(row, 'exceptored', status);
Expand Down Expand Up @@ -1477,26 +1481,16 @@ const reloadTab = function(ev) {
const toSummaryPaneFilterNode = async function(receiver, filter) {
receiver.children[1].textContent = filter;
if ( filterAuthorMode !== true ) { return; }
const match = /#@?#/.exec(filter);
if ( match === null ) { return; }
const fragment = document.createDocumentFragment();
const pos = match.index + match[0].length;
fragment.appendChild(document.createTextNode(filter.slice(0, pos)));
const selector = filter.slice(pos);
const span = document.createElement('span');
span.className = 'filter';
span.textContent = selector;
fragment.appendChild(span);
if ( dom.cl.has(targetRow, 'canLookup') === false ) { return; }
const exceptedFilter = dom.cl.has(targetRow, 'extendedRealm')
? `#@#${filter.replace(/^.*?#@?#/, '')}`
: `@@${filter.replace(/^@@/, '')}`;
const isTemporaryException = await messaging.send('loggerUI', {
what: 'hasTemporaryException',
filter,
what: 'hasInMemoryFilter',
filter: exceptedFilter,
});
dom.cl.toggle(receiver, 'exceptored', isTemporaryException);
if ( match[0] === '##' || isTemporaryException ) {
receiver.children[2].style.visibility = '';
}
receiver.children[1].textContent = '';
receiver.children[1].appendChild(fragment);
receiver.children[2].style.visibility = '';
};

const fillSummaryPaneFilterList = async function(rows) {
Expand Down
20 changes: 8 additions & 12 deletions src/js/logger.js
Expand Up @@ -32,19 +32,15 @@ let writePtr = 0;
const logBufferObsoleteAfter = 30 * 1000;

const janitor = ( ) => {
if (
buffer !== null &&
lastReadTime < (Date.now() - logBufferObsoleteAfter)
) {
logger.enabled = false;
buffer = null;
writePtr = 0;
logger.ownerId = undefined;
vAPI.messaging.broadcast({ what: 'loggerDisabled' });
}
if ( buffer !== null ) {
vAPI.setTimeout(janitor, logBufferObsoleteAfter);
if ( buffer === null ) { return; }
if ( lastReadTime >= (Date.now() - logBufferObsoleteAfter) ) {
return vAPI.setTimeout(janitor, logBufferObsoleteAfter);
}
logger.enabled = false;
buffer = null;
writePtr = 0;
logger.ownerId = undefined;
vAPI.messaging.broadcast({ what: 'loggerDisabled' });
};

const boxEntry = function(details) {
Expand Down
59 changes: 17 additions & 42 deletions src/js/messaging.js
Expand Up @@ -35,7 +35,6 @@ import logger from './logger.js';
import lz4Codec from './lz4.js';
import io from './assets.js';
import scriptletFilteringEngine from './scriptlet-filtering.js';
import staticExtFilteringEngine from './static-ext-filtering.js';
import staticFilteringReverseLookup from './reverselookup.js';
import staticNetFilteringEngine from './static-net-filtering.js';
import µb from './background.js';
Expand Down Expand Up @@ -305,6 +304,10 @@ const onMessage = function(request, sender, callback) {
µb.elementPickerExec(request.tabId, 0, request.targetURL, request.zap);
break;

case 'loggerDisabled':
µb.clearInMemoryFilters();
break;

case 'gotoURL':
µb.openNewTab(request.details);
break;
Expand Down Expand Up @@ -1680,49 +1683,25 @@ const getURLFilteringData = function(details) {
return response;
};

const compileTemporaryException = function(filter) {
const parser = new StaticFilteringParser({
nativeCssHas: vAPI.webextFlavor.env.includes('native_css_has'),
});
parser.analyze(filter);
if ( parser.shouldDiscard() ) { return; }
return staticExtFilteringEngine.compileTemporary(parser);
};

const toggleTemporaryException = function(details) {
const result = compileTemporaryException(details.filter);
if ( result === undefined ) { return false; }
const { session, selector } = result;
if ( session.has(1, selector) ) {
session.remove(1, selector);
return false;
}
session.add(1, selector);
return true;
};

const hasTemporaryException = function(details) {
const result = compileTemporaryException(details.filter);
if ( result === undefined ) { return false; }
const { session, selector } = result;
return session && session.has(1, selector);
};

const onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
case 'readAll':
if (
logger.ownerId !== undefined &&
logger.ownerId !== request.ownerId
) {
if ( logger.ownerId !== undefined && logger.ownerId !== request.ownerId ) {
return callback({ unavailable: true });
}
vAPI.tabs.getCurrent().then(tab => {
getLoggerData(request, tab && tab.id, callback);
});
return;

case 'toggleInMemoryFilter': {
const promise = µb.hasInMemoryFilter(request.filter)
? µb.removeInMemoryFilter(request.filter)
: µb.addInMemoryFilter(request.filter);
promise.then(status => { callback(status); });
return;
}
default:
break;
}
Expand All @@ -1731,14 +1710,14 @@ const onMessage = function(request, sender, callback) {
let response;

switch ( request.what ) {
case 'hasTemporaryException':
response = hasTemporaryException(request);
case 'hasInMemoryFilter':
response = µb.hasInMemoryFilter(request.filter);
break;

case 'releaseView':
if ( request.ownerId === logger.ownerId ) {
logger.ownerId = undefined;
}
if ( request.ownerId !== logger.ownerId ) { break; }
logger.ownerId = undefined;
µb.clearInMemoryFilters();
break;

case 'saveURLFilteringRules':
Expand All @@ -1761,10 +1740,6 @@ const onMessage = function(request, sender, callback) {
response = getURLFilteringData(request);
break;

case 'toggleTemporaryException':
response = toggleTemporaryException(request);
break;

default:
return vAPI.messaging.UNHANDLED;
}
Expand Down

2 comments on commit a91781a

@uBlock-user
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gorhill

  • Still no strikethrough line on HTML filters.

Secondly, do existing exception filters have any effect when exceptor widget is applied on them ? For e.g - #@#[id^="div-gpt-ad"] ?

@u-RraaLL
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just had some thoughts:

Hitting F5 by mistake when the logger is in the foreground acts as closing the logger - clears the temporary exceptions. Is it possible to lock this shortcut somehow for logger? Or can you find another way of preventing an accidental wipe?

What do you think about putting some kind of indicator in logger's output to differentiate between temporary and permanent exceptions? I mean without having to click every single one to see which ones are and aren't part of a filter lists.

And while I'm at it, how about changing the exceptor's highlight when activated from bluish to greenish?

Also, with the addition of temporary network exceptions this should now be a safer option for people to experiment with than Dynamic or URL Filtering. But the option itself is still hidden quite well, requiring the prior knowledge before users can enable it.
Should this perhaps be now made easier to access?
Then again, this restores allow rules in the overview panel, so maybe it'd be better to separate it into another option?

Please sign in to comment.