Skip to content

Commit

Permalink
Expand parser's ability to process static extended filtering
Browse files Browse the repository at this point in the history
This commit moves some of the parsing logic of static
extended filtering into the static filtering parser; this
allows better syntax highlighting and creation-time
error-catching for cosmetic, HTML, and scriptlet filters.
  • Loading branch information
gorhill committed Jun 13, 2020
1 parent 681bd70 commit 1a082e0
Show file tree
Hide file tree
Showing 10 changed files with 880 additions and 863 deletions.
5 changes: 4 additions & 1 deletion src/css/codemirror.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@
text-decoration-style: solid;
text-decoration-line: underline;
}
.cm-s-default .cm-error {
color: inherit;
}
.cm-s-default .cm-error,
.CodeMirror-linebackground.error {
background-color: #ff000018;
background-color: #ff000016;
text-decoration: underline red;
text-underline-position: under;
}
Expand Down
92 changes: 52 additions & 40 deletions src/js/codemirror/ubo-static-filtering.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,54 +29,35 @@ CodeMirror.defineMode("ubo-static-filtering", function() {
let parserSlot = 0;
let netOptionValueMode = false;

const colorSpan = function(stream) {
if ( parser.category === parser.CATNone || parser.shouldIgnore() ) {
stream.skipToEnd();
return 'comment';
}
if ( parser.category === parser.CATComment ) {
stream.skipToEnd();
return reDirective.test(stream.string)
? 'variable strong'
: 'comment';
}
if ( (parser.slices[parserSlot] & parser.BITIgnore) !== 0 ) {
const colorExtSpan = function(stream) {
if ( parserSlot < parser.optionsAnchorSpan.i ) {
const style = (parser.slices[parserSlot] & parser.BITComma) === 0
? 'string-2'
: 'def';
stream.pos += parser.slices[parserSlot+2];
parserSlot += 3;
return 'comment';
return style;
}
if ( (parser.slices[parserSlot] & parser.BITError) !== 0 ) {
if (
parserSlot >= parser.optionsAnchorSpan.i &&
parserSlot < parser.patternSpan.i
) {
const style = (parser.flavorBits & parser.BITFlavorException) !== 0
? 'tag'
: 'def';
stream.pos += parser.slices[parserSlot+2];
parserSlot += 3;
return 'error';
return `${style} strong`;
}
if ( parser.category === parser.CATStaticExtFilter ) {
if ( parserSlot < parser.optionsAnchorSpan.i ) {
const style = (parser.slices[parserSlot] & parser.BITComma) === 0
? 'string-2'
: 'def';
stream.pos += parser.slices[parserSlot+2];
parserSlot += 3;
return style;
}
if (
parserSlot >= parser.optionsAnchorSpan.i &&
parserSlot < parser.patternSpan.i
) {
const style = (parser.flavorBits & parser.BITFlavorException) !== 0
? 'tag'
: 'def';
stream.pos += parser.slices[parserSlot+2];
parserSlot += 3;
return `${style} strong`;
}
if ( parserSlot >= parser.patternSpan.i ) {
stream.skipToEnd();
return 'variable';
}
if ( parserSlot >= parser.patternSpan.i ) {
stream.skipToEnd();
return '';
return 'variable';
}
stream.skipToEnd();
return '';
};

const colorNetSpan = function(stream) {
if ( parserSlot < parser.exceptionSpan.i ) {
stream.pos += parser.slices[parserSlot+2];
parserSlot += 3;
Expand Down Expand Up @@ -165,6 +146,37 @@ CodeMirror.defineMode("ubo-static-filtering", function() {
return '';
};

const colorSpan = function(stream) {
if ( parser.category === parser.CATNone || parser.shouldIgnore() ) {
stream.skipToEnd();
return 'comment';
}
if ( parser.category === parser.CATComment ) {
stream.skipToEnd();
return reDirective.test(stream.string)
? 'variable strong'
: 'comment';
}
if ( (parser.slices[parserSlot] & parser.BITIgnore) !== 0 ) {
stream.pos += parser.slices[parserSlot+2];
parserSlot += 3;
return 'comment';
}
if ( (parser.slices[parserSlot] & parser.BITError) !== 0 ) {
stream.pos += parser.slices[parserSlot+2];
parserSlot += 3;
return 'error';
}
if ( parser.category === parser.CATStaticExtFilter ) {
return colorExtSpan(stream);
}
if ( parser.category === parser.CATStaticNetFilter ) {
return colorNetSpan(stream);
}
stream.skipToEnd();
return null;
};

return {
token: function(stream) {
if ( stream.sol() ) {
Expand Down
104 changes: 48 additions & 56 deletions src/js/cosmetic-filtering.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,7 @@ const FilterContainer = function() {
this.specificFilters = new µb.staticExtFilteringEngine.HostnameBasedDB(2);

// temporary filters
this.sessionFilterDB = new (
class extends µb.staticExtFilteringEngine.SessionDB {
compile(s) {
return µb.staticExtFilteringEngine.compileSelector(s);
}
}
)();
this.sessionFilterDB = new µb.staticExtFilteringEngine.SessionDB();

// low generic cosmetic filters, organized by id/class then simple/complex.
this.lowlyGeneric = Object.create(null);
Expand Down Expand Up @@ -351,53 +345,60 @@ FilterContainer.prototype.keyFromSelector = function(selector) {

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

FilterContainer.prototype.compile = function(parsed, writer) {
FilterContainer.prototype.compile = function(parser, writer) {
// 1000 = cosmetic filtering
writer.select(1000);

const hostnames = parsed.hostnames;
let i = hostnames.length;
if ( i === 0 ) {
this.compileGenericSelector(parsed, writer);
if ( parser.hasOptions() === false ) {
this.compileGenericSelector(parser, writer);
return true;
}

// https://github.com/chrisaljoudi/uBlock/issues/151
// Negated hostname means the filter applies to all non-negated hostnames
// of same filter OR globally if there is no non-negated hostnames.
let applyGlobally = true;
while ( i-- ) {
const hostname = hostnames[i];
if ( hostname.startsWith('~') === false ) {
for ( const { hn, not, bad } of parser.extOptions() ) {
if ( bad ) { continue; }
if ( not === false ) {
applyGlobally = false;
}
this.compileSpecificSelector(hostname, parsed, writer);
this.compileSpecificSelector(parser, hn, not, writer);
}
if ( applyGlobally ) {
this.compileGenericSelector(parsed, writer);
this.compileGenericSelector(parser, writer);
}

return true;
};

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

FilterContainer.prototype.compileGenericSelector = function(parsed, writer) {
if ( parsed.exception === false ) {
this.compileGenericHideSelector(parsed, writer);
FilterContainer.prototype.compileGenericSelector = function(parser, writer) {
if ( parser.isException() ) {
this.compileGenericUnhideSelector(parser, writer);
} else {
this.compileGenericUnhideSelector(parsed, writer);
this.compileGenericHideSelector(parser, writer);
}
};

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

FilterContainer.prototype.compileGenericHideSelector = function(
parsed,
parser,
writer
) {
const selector = parsed.suffix;
const type = selector.charCodeAt(0);
const { raw, compiled, pseudoclass } = parser.result;
if ( compiled === undefined ) {
const who = writer.properties.get('assetKey') || '?';
µb.logger.writeOne({
realm: 'message',
type: 'error',
text: `Invalid generic cosmetic filter in ${who}: ${raw}`
});
}

const type = compiled.charCodeAt(0);
let key;

// Simple selector-based CSS rule: no need to test for whether the
Expand All @@ -406,21 +407,19 @@ FilterContainer.prototype.compileGenericHideSelector = function(
// - ###ad-bigbox
// - ##.ads-bigbox
if ( type === 0x23 /* '#' */ ) {
key = this.keyFromSelector(selector);
if ( key === selector ) {
key = this.keyFromSelector(compiled);
if ( key === compiled ) {
writer.push([ 0, key.slice(1) ]);
return;
}
} else if ( type === 0x2E /* '.' */ ) {
key = this.keyFromSelector(selector);
if ( key === selector ) {
key = this.keyFromSelector(compiled);
if ( key === compiled ) {
writer.push([ 2, key.slice(1) ]);
return;
}
}

const compiled = µb.staticExtFilteringEngine.compileSelector(selector);

// Invalid cosmetic filter, possible reasons:
// - Bad syntax
// - Procedural filters (can't be generic): the compiled version of
Expand All @@ -431,19 +430,15 @@ FilterContainer.prototype.compileGenericHideSelector = function(
// https://github.com/uBlockOrigin/uBlock-issues/issues/131
// Support generic procedural filters as per advanced settings.
// TODO: prevent double compilation.
if (
compiled === undefined ||
compiled !== selector &&
µb.staticExtFilteringEngine.compileSelector.pseudoclass === -1
) {
if ( compiled !== raw && pseudoclass === false ) {
if ( µb.hiddenSettings.allowGenericProceduralFilters === true ) {
return this.compileSpecificSelector('', parsed, writer);
return this.compileSpecificSelector(parser, '', false, writer);
}
const who = writer.properties.get('assetKey') || '?';
µb.logger.writeOne({
realm: 'message',
type: 'error',
text: `Invalid generic cosmetic filter in ${who}: ##${selector}`
text: `Invalid generic cosmetic filter in ${who}: ##${raw}`
});
return;
}
Expand All @@ -455,21 +450,21 @@ FilterContainer.prototype.compileGenericHideSelector = function(
writer.push([
type === 0x23 /* '#' */ ? 1 : 3,
key.slice(1),
selector
compiled
]);
return;
}

// https://github.com/gorhill/uBlock/issues/909
// Anything which contains a plain id/class selector can be classified
// as a low generic cosmetic filter.
const matches = this.rePlainSelectorEx.exec(selector);
const matches = this.rePlainSelectorEx.exec(compiled);
if ( matches !== null ) {
const key = matches[1] || matches[2];
writer.push([
key.charCodeAt(0) === 0x23 /* '#' */ ? 1 : 3,
key.slice(1),
selector
compiled
]);
return;
}
Expand All @@ -479,27 +474,27 @@ FilterContainer.prototype.compileGenericHideSelector = function(
// For efficiency purpose, we will distinguish between simple and complex
// selectors.

if ( this.reSimpleHighGeneric.test(selector) ) {
writer.push([ 4 /* simple */, selector ]);
if ( this.reSimpleHighGeneric.test(compiled) ) {
writer.push([ 4 /* simple */, compiled ]);
} else {
writer.push([ 5 /* complex */, selector ]);
writer.push([ 5 /* complex */, compiled ]);
}
};

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

FilterContainer.prototype.compileGenericUnhideSelector = function(
parsed,
parser,
writer
) {
// Procedural cosmetic filters are acceptable as generic exception filters.
const compiled = µb.staticExtFilteringEngine.compileSelector(parsed.suffix);
const { raw, compiled } = parser.result;
if ( compiled === undefined ) {
const who = writer.properties.get('assetKey') || '?';
µb.logger.writeOne({
realm: 'message',
type: 'error',
text: `Invalid cosmetic filter in ${who}: #@#${parsed.suffix}`
text: `Invalid cosmetic filter in ${who}: #@#${raw}`
});
return;
}
Expand All @@ -516,28 +511,25 @@ FilterContainer.prototype.compileGenericUnhideSelector = function(
/******************************************************************************/

FilterContainer.prototype.compileSpecificSelector = function(
parser,
hostname,
parsed,
not,
writer
) {
// https://github.com/chrisaljoudi/uBlock/issues/145
let unhide = parsed.exception ? 1 : 0;
if ( hostname.startsWith('~') ) {
hostname = hostname.slice(1);
unhide ^= 1;
}

const compiled = µb.staticExtFilteringEngine.compileSelector(parsed.suffix);
const { raw, compiled, exception } = parser.result;
if ( compiled === undefined ) {
const who = writer.properties.get('assetKey') || '?';
µb.logger.writeOne({
realm: 'message',
type: 'error',
text: `Invalid cosmetic filter in ${who}: ##${parsed.suffix}`
text: `Invalid cosmetic filter in ${who}: ##${raw}`
});
return;
}

// https://github.com/chrisaljoudi/uBlock/issues/145
let unhide = exception ? 1 : 0;
if ( not ) { unhide ^= 1; }

let kind = 0;
if ( unhide === 1 ) {
Expand Down
Loading

0 comments on commit 1a082e0

Please sign in to comment.