Skip to content

Commit 8b696a6

Browse files
committed
Add path support as target option in static extended filtering
Support for paths allows to narrow down specific static extended filters to specific webpages on a given site. Examples of usage: example.com/toto##h1 /example\.com\/toto\d+/#@#h1
1 parent 370107b commit 8b696a6

22 files changed

+671
-606
lines changed

platform/chromium/vapi-background-ext.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ vAPI.scriptletsInjector = (( ) => {
212212
const parts = [
213213
'(',
214214
function(details) {
215-
if ( typeof self.uBO_scriptletsInjected === 'string' ) { return; }
215+
if ( self.uBO_scriptletsInjected !== undefined ) { return; }
216216
const doc = document;
217217
const { location } = doc;
218218
if ( location === null ) { return; }

platform/firefox/vapi-background-ext.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ vAPI.scriptletsInjector = (( ) => {
355355
const parts = [
356356
'(',
357357
function(details) {
358-
if ( typeof self.uBO_scriptletsInjected === 'string' ) { return; }
358+
if ( self.uBO_scriptletsInjected !== undefined ) { return; }
359359
const doc = document;
360360
const { location } = doc;
361361
if ( location === null ) { return; }

src/js/background.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,8 @@ const µBlock = { // jshint ignore:line
180180

181181
// Read-only
182182
systemSettings: {
183-
compiledMagic: 57, // Increase when compiled format changes
184-
selfieMagic: 59, // Increase when selfie format changes
183+
compiledMagic: 60, // Increase when compiled format changes
184+
selfieMagic: 60, // Increase when selfie format changes
185185
},
186186

187187
// https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501

src/js/codemirror/ubo-static-filtering.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import * as sfp from '../static-filtering-parser.js';
2727
import { dom, qs$ } from '../dom.js';
28+
import { tokenizableStrFromRegex } from '../regex-analyzer.js';
2829

2930
/******************************************************************************/
3031

@@ -67,6 +68,8 @@ const uBOStaticFilteringMode = (( ) => {
6768
) !== 0;
6869
};
6970

71+
const reGoodRegexToken = /[^\x01%0-9A-Za-z][%0-9A-Za-z]{7,}|[^\x01%0-9A-Za-z][%0-9A-Za-z]{1,6}[^\x01%0-9A-Za-z]/;
72+
7073
const colorFromAstNode = mode => {
7174
if ( mode.astParser.nodeIsEmptyString(mode.currentWalkerNode) ) { return '+'; }
7275
if ( nodeHasError(mode) ) { return 'error'; }
@@ -119,7 +122,9 @@ const uBOStaticFilteringMode = (( ) => {
119122
case sfp.NODE_TYPE_NET_PATTERN:
120123
if ( mode.astWalker.canGoDown() ) { break; }
121124
if ( mode.astParser.isRegexPattern() ) {
122-
if ( mode.astParser.getNodeFlags(mode.currentWalkerNode, sfp.NODE_FLAG_PATTERN_UNTOKENIZABLE) !== 0 ) {
125+
const s = mode.astParser.getNodeString(mode.currentWalkerNode);
126+
const tokenizable = tokenizableStrFromRegex(s);
127+
if ( reGoodRegexToken.test(tokenizable) === false ) {
123128
return 'variable warning';
124129
}
125130
return 'variable notice';

src/js/contentscript.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1312,7 +1312,7 @@ vAPI.DOMFilterer = class {
13121312
vAPI.messaging.send('contentscript', {
13131313
what: 'retrieveContentScriptParameters',
13141314
url: vAPI.effectiveSelf.location.href,
1315-
needScriptlets: typeof self.uBO_scriptletsInjected !== 'string',
1315+
needScriptlets: self.uBO_scriptletsInjected === undefined,
13161316
}).then(response => {
13171317
onResponseReady(response);
13181318
});

src/js/cosmetic-filtering.js

Lines changed: 36 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ const CosmeticFilteringEngine = function() {
235235
});
236236

237237
// specific filters
238-
this.specificFilters = new StaticExtFilteringHostnameDB(2);
238+
this.specificFilters = new StaticExtFilteringHostnameDB();
239239

240240
// low generic cosmetic filters: map of hash => stringified selector list
241241
this.lowlyGeneric = new Map();
@@ -254,16 +254,6 @@ const CosmeticFilteringEngine = function() {
254254
str: '',
255255
mru: new MRUCache(16)
256256
};
257-
258-
// Short-lived: content is valid only during one function call. These
259-
// is to prevent repeated allocation/deallocation overheads -- the
260-
// constructors/destructors of javascript Set/Map is assumed to be costlier
261-
// than just calling clear() on these.
262-
this.$specificSet = new Set();
263-
this.$exceptionSet = new Set();
264-
this.$proceduralSet = new Set();
265-
this.$dummySet = new Set();
266-
267257
this.reset();
268258
};
269259

@@ -429,7 +419,7 @@ CosmeticFilteringEngine.prototype.compileGenericUnhideSelector = function(
429419
// hostnames). No distinction is made between declarative and
430420
// procedural selectors, since they really exist only to cancel
431421
// out other cosmetic filters.
432-
writer.push([ 8, '', 0b001, compiled ]);
422+
writer.push([ 8, '', `-${compiled}` ]);
433423
};
434424

435425
/******************************************************************************/
@@ -452,23 +442,8 @@ CosmeticFilteringEngine.prototype.compileSpecificSelector = function(
452442
}
453443

454444
writer.select('COSMETIC_FILTERS:SPECIFIC');
455-
456-
// https://github.com/chrisaljoudi/uBlock/issues/145
457-
let unhide = exception ? 1 : 0;
458-
if ( not ) { unhide ^= 1; }
459-
460-
let kind = 0;
461-
if ( unhide === 1 ) {
462-
kind |= 0b001; // Exception
463-
}
464-
if ( compiled.charCodeAt(0) === 0x7B /* '{' */ ) {
465-
kind |= 0b010; // Procedural
466-
}
467-
if ( hostname === '*' ) {
468-
kind |= 0b100; // Applies everywhere
469-
}
470-
471-
writer.push([ 8, hostname, kind, compiled ]);
445+
const prefix = ((exception ? 1 : 0) ^ (not ? 1 : 0)) ? '-' : '+';
446+
writer.push([ 8, hostname, `${prefix}${compiled}` ]);
472447
};
473448

474449
/******************************************************************************/
@@ -500,15 +475,16 @@ CosmeticFilteringEngine.prototype.fromCompiledContent = function(reader, options
500475
// not to be injected conditionally through the DOM surveyor.
501476
// hash, *, .promoted-tweet
502477
case 8:
503-
if ( args[2] === 0b100 ) {
504-
if ( this.reSimpleHighGeneric.test(args[3]) )
505-
this.highlyGeneric.simple.dict.add(args[3]);
478+
if ( args[1] === '*' ) {
479+
const selector = args[2].slice(1);
480+
if ( this.reSimpleHighGeneric.test(selector) )
481+
this.highlyGeneric.simple.dict.add(selector);
506482
else {
507-
this.highlyGeneric.complex.dict.add(args[3]);
483+
this.highlyGeneric.complex.dict.add(selector);
508484
}
509485
break;
510486
}
511-
this.specificFilters.store(args[1], args[2] & 0b011, args[3]);
487+
this.specificFilters.store(args[1], args[2]);
512488
break;
513489
default:
514490
this.discardedCount += 1;
@@ -590,9 +566,7 @@ CosmeticFilteringEngine.prototype.toSelfie = function() {
590566

591567
CosmeticFilteringEngine.prototype.fromSelfie = function(selfie) {
592568
if ( selfie.version !== this.selfieVersion ) {
593-
throw new Error(
594-
`cosmeticFilteringEngine: mismatched selfie version, ${selfie.version}, expected ${this.selfieVersion}`
595-
);
569+
throw new TypeError('Bad selfie');
596570
}
597571
this.acceptedCount = selfie.acceptedCount;
598572
this.discardedCount = selfie.discardedCount;
@@ -630,16 +604,11 @@ CosmeticFilteringEngine.prototype.removeFromSelectorCache = function(
630604
type = undefined
631605
) {
632606
const targetHostnameLength = targetHostname.length;
633-
for ( let entry of this.selectorCache ) {
634-
let hostname = entry[0];
635-
let item = entry[1];
607+
for ( const [ hostname, item ] of this.selectorCache ) {
636608
if ( targetHostname !== '*' ) {
637609
if ( hostname.endsWith(targetHostname) === false ) { continue; }
638-
if (
639-
hostname.length !== targetHostnameLength &&
640-
hostname.charAt(hostname.length - targetHostnameLength - 1) !== '.'
641-
) {
642-
continue;
610+
if ( hostname.length !== targetHostnameLength ) {
611+
if ( hostname.at(-1) !== '.' ) { continue; }
643612
}
644613
}
645614
item.remove(type);
@@ -791,48 +760,39 @@ CosmeticFilteringEngine.prototype.retrieveSpecificSelectors = function(
791760
options.noSpecificCosmeticFiltering !== true ||
792761
options.noGenericCosmeticFiltering !== true
793762
) {
794-
const specificSet = this.$specificSet;
795-
const proceduralSet = this.$proceduralSet;
796-
const exceptionSet = this.$exceptionSet;
797-
const dummySet = this.$dummySet;
798-
799763
// Cached cosmetic filters: these are always declarative.
764+
const specificSet = new Set();
800765
if ( cacheEntry !== undefined ) {
801766
cacheEntry.retrieveCosmetic(specificSet, out.genericCosmeticHashes = []);
802767
if ( cacheEntry.disableSurveyor ) {
803768
out.disableSurveyor = true;
804769
}
805770
}
806771

772+
const allSet = new Set();
807773
// Retrieve filters with a non-empty hostname
808-
const retrieveSets = [ specificSet, exceptionSet, proceduralSet, exceptionSet ];
809-
const discardSets = [ dummySet, exceptionSet ];
810-
this.specificFilters.retrieve(
811-
hostname,
812-
options.noSpecificCosmeticFiltering ? discardSets : retrieveSets,
813-
1
814-
);
815-
// Retrieve filters with a regex-based hostname value
816-
this.specificFilters.retrieve(
817-
hostname,
818-
options.noSpecificCosmeticFiltering ? discardSets : retrieveSets,
819-
3
820-
);
774+
this.specificFilters.retrieveSpecifics(allSet, hostname);
821775
// Retrieve filters with a entity-based hostname value
822776
const entity = entityFromHostname(hostname, request.domain);
823-
if ( entity !== '' ) {
824-
this.specificFilters.retrieve(
825-
entity,
826-
options.noSpecificCosmeticFiltering ? discardSets : retrieveSets,
827-
1
828-
);
829-
}
777+
this.specificFilters.retrieveSpecifics(allSet, entity);
778+
// Retrieve filters with a regex-based hostname value
779+
this.specificFilters.retrieveSpecificsByRegex(allSet, hostname, request.url);
830780
// Retrieve filters with an empty hostname
831-
this.specificFilters.retrieve(
832-
hostname,
833-
options.noGenericCosmeticFiltering ? discardSets : retrieveSets,
834-
2
835-
);
781+
this.specificFilters.retrieveGenerics(allSet);
782+
783+
// Split filters in different groups
784+
const proceduralSet = new Set();
785+
const exceptionSet = new Set();
786+
for ( const s of allSet ) {
787+
const selector = s.slice(1);
788+
if ( s.charCodeAt(0) === 0x2D /* - */ ) {
789+
exceptionSet.add(selector);
790+
} else if ( selector.charCodeAt(0) === 0x7B /* { */ ) {
791+
proceduralSet.add(selector);
792+
} else {
793+
specificSet.add(selector);
794+
}
795+
}
836796

837797
// Apply exceptions to specific filterset
838798
if ( exceptionSet.size !== 0 ) {
@@ -855,12 +815,12 @@ CosmeticFilteringEngine.prototype.retrieveSpecificSelectors = function(
855815
// filters, so we extract and inject them immediately.
856816
if ( proceduralSet.size !== 0 ) {
857817
for ( const json of proceduralSet ) {
858-
const pfilter = JSON.parse(json);
859818
if ( exceptionSet.has(json) ) {
860819
proceduralSet.delete(json);
861820
out.exceptedFilters.push(json);
862821
continue;
863822
}
823+
const pfilter = JSON.parse(json);
864824
if ( exceptionSet.has(pfilter.raw) ) {
865825
proceduralSet.delete(json);
866826
out.exceptedFilters.push(pfilter.raw);
@@ -914,12 +874,6 @@ CosmeticFilteringEngine.prototype.retrieveSpecificSelectors = function(
914874
}
915875
}
916876
}
917-
918-
// Important: always clear used registers before leaving.
919-
specificSet.clear();
920-
proceduralSet.clear();
921-
exceptionSet.clear();
922-
dummySet.clear();
923877
}
924878

925879
const details = {

0 commit comments

Comments
 (0)