diff --git a/src/js/codemirror/ubo-static-filtering.js b/src/js/codemirror/ubo-static-filtering.js index d7d3755a3..475a3458d 100644 --- a/src/js/codemirror/ubo-static-filtering.js +++ b/src/js/codemirror/ubo-static-filtering.js @@ -79,9 +79,6 @@ CodeMirror.defineMode('ubo-static-filtering', function() { const raw = astParser.getNodeString(currentWalkerNode); const not = raw.startsWith('!'); const token = not ? raw.slice(1) : raw; - if ( preparseDirectiveTokens.has(token) === false ) { - return 'error strong'; - } return not === preparseDirectiveTokens.get(token) ? 'negative strong' : 'positive strong'; @@ -671,38 +668,9 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => { nativeCssHas: vAPI.webextFlavor.env.includes('native_css_has'), }); - let errorCount = 0; - let markedsetStart = 0; - let markedsetTimer; - - const processMarkedsetAsync = doc => { - if ( markedsetTimer !== undefined ) { return; } - markedsetTimer = self.requestIdleCallback(deadline => { - markedsetTimer = undefined; - processMarkedset(doc, deadline); - }); - }; - - const processMarkedset = (doc, deadline) => { - const lineCount = doc.lineCount(); - doc.eachLine(markedsetStart, lineCount, lineHandle => { - const line = markedsetStart++; - const markers = lineHandle.gutterMarkers || null; - if ( markers && markers['CodeMirror-lintgutter'] ) { - errorCount += 1; - } - if ( (line & 0x0F) === 0 && deadline.timeRemaining() === 0 ) { - processMarkedsetAsync(doc); - return true; - } - if ( markedsetStart === lineCount ) { - CodeMirror.signal(doc.getEditor(), 'linterDone', { errorCount }); - } - }); - }; - const changeset = []; let changesetTimer; + let errorCount = 0; const addChange = (doc, change) => { changeset.push(change); @@ -711,10 +679,6 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => { const processChangesetAsync = doc => { if ( changesetTimer !== undefined ) { return; } - if ( markedsetTimer ) { - self.cancelIdleCallback(markedsetTimer); - markedsetTimer = undefined; - } changesetTimer = self.requestIdleCallback(deadline => { changesetTimer = undefined; processChangeset(doc, deadline); @@ -722,28 +686,27 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => { }; const extractError = ( ) => { - if ( astParser.isComment() ) { return; } - if ( astParser.isFilter() === false ) { return; } if ( astParser.hasError() === false ) { return; } - let error = 'Invalid filter'; + const error = 'Invalid filter'; + switch ( astParser.astError ) { + case sfp.AST_ERROR_REGEX: + return `${error}: Bad regular expression`; + case sfp.AST_ERROR_PATTERN: + return `${error}: Bad pattern`; + case sfp.AST_ERROR_DOMAIN_NAME: + return `${error}: Bad domain name`; + case sfp.AST_ERROR_OPTION_DUPLICATE: + return `${error}: Duplicate filter option`; + case sfp.AST_ERROR_OPTION_UNKNOWN: + return `${error}: Unsupported filter option`; + case sfp.AST_ERROR_IF_TOKEN_UNKNOWN: + return `${error}: Unknown preparsing token`; + default: + break; + } if ( astParser.isCosmeticFilter() && astParser.result.error ) { return `${error}: ${astParser.result.error}`; } - if ( astParser.astError === sfp.AST_ERROR_REGEX ) { - return `${error}: Bad regular expression`; - } - if ( astParser.astError === sfp.AST_ERROR_PATTERN ) { - return `${error}: Bad pattern`; - } - if ( astParser.astError === sfp.AST_ERROR_DOMAIN_NAME ) { - return `${error}: Bad domain name`; - } - if ( astParser.astError === sfp.AST_ERROR_OPTION_DUPLICATE ) { - return `${error}: Duplicate filter option`; - } - if ( astParser.astError === sfp.AST_ERROR_OPTION_UNKNOWN ) { - return `${error}: Unsupported filter option`; - } return error; }; @@ -764,12 +727,23 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => { const makeMarker = (doc, lineHandle, marker, error) => { if ( marker === undefined ) { - marker = markerTemplate.cloneNode(true); - doc.setGutterMarker(lineHandle, 'CodeMirror-lintgutter', marker); + marker = addMarker(doc, lineHandle); } marker.children[0].textContent = error; }; + const addMarker = (doc, lineHandle) => { + const marker = markerTemplate.cloneNode(true); + doc.setGutterMarker(lineHandle, 'CodeMirror-lintgutter', marker); + lineHandle.on('delete', deleteMarker); + errorCount += 1; + return marker; + }; + + const deleteMarker = ( ) => { + errorCount -= 1; + }; + const processChange = (doc, deadline, change) => { let { from, to } = change; doc.eachLine(from, to, lineHandle => { @@ -804,9 +778,7 @@ CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => { if ( changeset.length !== 0 ) { return processChangesetAsync(doc); } - errorCount = 0; - markedsetStart = 0; - processMarkedsetAsync(doc); + CodeMirror.signal(doc.getEditor(), 'linterDone', { errorCount }); }; CodeMirror.defineInitHook(cm => { diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index 54d0c082a..39258e9cb 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -76,6 +76,7 @@ export const AST_TYPE_EXTENDED_COSMETIC = iota++; export const AST_TYPE_EXTENDED_SCRIPTLET = iota++; export const AST_TYPE_EXTENDED_HTML = iota++; export const AST_TYPE_EXTENDED_RESPONSEHEADER = iota++; +export const AST_TYPE_COMMENT_PREPARSER = iota++; iota = 0; export const AST_FLAG_UNSUPPORTED = 1 << iota++; @@ -97,6 +98,7 @@ export const AST_ERROR_PATTERN = 1 << iota++; export const AST_ERROR_DOMAIN_NAME = 1 << iota++; export const AST_ERROR_OPTION_DUPLICATE = 1 << iota++; export const AST_ERROR_OPTION_UNKNOWN = 1 << iota++; +export const AST_ERROR_IF_TOKEN_UNKNOWN = 1 << iota++; iota = 0; const NODE_RIGHT_INDEX = iota++; @@ -553,6 +555,34 @@ export const removableHTTPHeaders = new Set([ 'set-cookie', ]); +export const preparserIfTokens = new Set([ + 'ext_ublock', + 'ext_ubol', + 'ext_devbuild', + 'env_chromium', + 'env_edge', + 'env_firefox', + 'env_legacy', + 'env_mobile', + 'env_mv3', + 'env_safari', + 'cap_html_filtering', + 'cap_user_stylesheet', + 'false', + 'ext_abp', + 'adguard', + 'adguard_app_android', + 'adguard_app_ios', + 'adguard_app_mac', + 'adguard_app_windows', + 'adguard_ext_android_cb', + 'adguard_ext_chromium', + 'adguard_ext_edge', + 'adguard_ext_firefox', + 'adguard_ext_opera', + 'adguard_ext_safari', +]); + /******************************************************************************/ const exCharCodeAt = (s, i) => { @@ -1049,6 +1079,7 @@ export class AstFilterParser { parseComment(parent) { const parentStr = this.getNodeString(parent); if ( this.rePreparseDirectiveAny.test(parentStr) ) { + this.astTypeFlavor = AST_TYPE_COMMENT_PREPARSER; return this.parsePreparseDirective(parent, parentStr); } if ( this.reURL.test(parentStr) === false ) { return 0; } @@ -1078,14 +1109,22 @@ export class AstFilterParser { directiveEnd ); if ( directiveEnd !== parentEnd ) { - const next = this.allocTypedNode( - s .startsWith('!#if ') - ? NODE_TYPE_PREPARSE_DIRECTIVE_IF_VALUE - : NODE_TYPE_PREPARSE_DIRECTIVE_VALUE, - directiveEnd, - parentEnd - ); + const type = s.startsWith('!#if ') + ? NODE_TYPE_PREPARSE_DIRECTIVE_IF_VALUE + : NODE_TYPE_PREPARSE_DIRECTIVE_VALUE; + const next = this.allocTypedNode(type, directiveEnd, parentEnd); this.linkRight(head, next); + if ( type === NODE_TYPE_PREPARSE_DIRECTIVE_IF_VALUE ) { + const rawToken = this.getNodeString(next).trim(); + const token = rawToken.charCodeAt(0) === 0x21 /* ! */ + ? rawToken.slice(1) + : rawToken; + if ( preparserIfTokens.has(token) === false ) { + this.addNodeFlags(next, NODE_FLAG_ERROR); + this.addFlags(AST_FLAG_HAS_ERROR); + this.astError = AST_ERROR_IF_TOKEN_UNKNOWN; + } + } } return head; }