From 90c7e79f4f30ca9ad93be507009a75f009672941 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 1 Aug 2020 10:32:40 -0400 Subject: [PATCH] Consolidate filter list reverse lookup code into a single file Since it's possible to execute specific code paths according to whether the context is that of a worker or not, it's possible to keep the main/thread code in a single file. Keeping the main/worker code paths into a single file is more convenient for both code maintenance and code review. --- src/js/reverselookup-worker.js | 293 ---------------- src/js/reverselookup.js | 618 +++++++++++++++++++++++---------- 2 files changed, 441 insertions(+), 470 deletions(-) delete mode 100644 src/js/reverselookup-worker.js diff --git a/src/js/reverselookup-worker.js b/src/js/reverselookup-worker.js deleted file mode 100644 index 9aee1f9a4..000000000 --- a/src/js/reverselookup-worker.js +++ /dev/null @@ -1,293 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2015-present Raymond Hill - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see {http://www.gnu.org/licenses/}. - - Home: https://github.com/gorhill/uBlock -*/ - -/* global onmessage, postMessage */ - -'use strict'; - -/******************************************************************************/ - -const reBlockStart = /^#block-start-(\d+)\n/gm; -let listEntries = Object.create(null); - -/******************************************************************************/ - -const extractBlocks = function(content, begId, endId) { - reBlockStart.lastIndex = 0; - const out = []; - let match = reBlockStart.exec(content); - while ( match !== null ) { - const beg = match.index + match[0].length; - const blockId = parseInt(match[1], 10); - if ( blockId >= begId && blockId < endId ) { - const end = content.indexOf('#block-end-' + match[1], beg); - out.push(content.slice(beg, end)); - reBlockStart.lastIndex = end; - } - match = reBlockStart.exec(content); - } - return out.join('\n'); -}; - -/******************************************************************************/ - -// https://github.com/MajkiIT/polish-ads-filter/issues/14768#issuecomment-536006312 -// Avoid reporting badfilter-ed filters. - -const fromNetFilter = function(details) { - const lists = []; - const compiledFilter = details.compiledFilter; - - for ( const assetKey in listEntries ) { - const entry = listEntries[assetKey]; - if ( entry === undefined ) { continue; } - const content = extractBlocks(entry.content, 0, 1); - let pos = 0; - for (;;) { - pos = content.indexOf(compiledFilter, pos); - if ( pos === -1 ) { break; } - // We need an exact match. - // https://github.com/gorhill/uBlock/issues/1392 - // https://github.com/gorhill/uBlock/issues/835 - const notFound = pos !== 0 && content.charCodeAt(pos - 1) !== 0x0A; - pos += compiledFilter.length; - if ( - notFound || - pos !== content.length && content.charCodeAt(pos) !== 0x0A - ) { - continue; - } - lists.push({ - assetKey: assetKey, - title: entry.title, - supportURL: entry.supportURL - }); - break; - } - } - - const response = {}; - response[details.rawFilter] = lists; - - postMessage({ - id: details.id, - response: response - }); -}; - -/******************************************************************************/ - -// Looking up filter lists from a cosmetic filter is a bit more complicated -// than with network filters: -// -// The filter is its raw representation, not its compiled version. This is -// because the cosmetic filtering engine can't translate a live cosmetic -// filter into its compiled version. Reason is I do not want to burden -// cosmetic filtering with the resource overhead of being able to re-compile -// live cosmetic filters. I want the cosmetic filtering code to be left -// completely unaffected by reverse lookup requirements. -// -// Mainly, given a CSS selector and a hostname as context, we will derive -// various versions of compiled filters and see if there are matches. This way -// the whole CPU cost is incurred by the reverse lookup code -- in a worker -// thread, and the cosmetic filtering engine incurs no cost at all. -// -// For this though, the reverse lookup code here needs some knowledge of -// the inners of the cosmetic filtering engine. -// FilterContainer.fromCompiledContent() is our reference code to create -// the various compiled versions. - -const fromCosmeticFilter = function(details) { - const match = /^#@?#\^?/.exec(details.rawFilter); - const prefix = match[0]; - const exception = prefix.charAt(1) === '@'; - const selector = details.rawFilter.slice(prefix.length); - const isHtmlFilter = prefix.endsWith('^'); - const hostname = details.hostname; - - // The longer the needle, the lower the number of false positives. - // https://github.com/uBlockOrigin/uBlock-issues/issues/1139 - // Mind that there is no guarantee a selector has `\w` characters. - const needle = selector.match(/\w+|\*/g).reduce(function(a, b) { - return a.length > b.length ? a : b; - }); - - const regexFromLabels = (prefix, hn, suffix) => - new RegExp( - prefix + - hn.split('.').reduce((acc, item) => `(${acc}\\.)?${item}`) + - suffix - ); - - // https://github.com/uBlockOrigin/uBlock-issues/issues/803 - // Support looking up selectors of the form `*##...` - const reHostname = regexFromLabels('^', hostname, '$'); - let reEntity; - { - const domain = details.domain; - const pos = domain.indexOf('.'); - if ( pos !== -1 ) { - reEntity = regexFromLabels( - '^(', - hostname.slice(0, pos + hostname.length - domain.length), - '\\.)?\\*$' - ); - } - } - - const hostnameMatches = hn => { - return hn === '' || - reHostname.test(hn) || - reEntity !== undefined && reEntity.test(hn); - }; - - const response = Object.create(null); - - for ( const assetKey in listEntries ) { - const entry = listEntries[assetKey]; - if ( entry === undefined ) { continue; } - let content = extractBlocks(entry.content, 1000, 2000), - isProcedural, - found; - let pos = 0; - while ( (pos = content.indexOf(needle, pos)) !== -1 ) { - let beg = content.lastIndexOf('\n', pos); - if ( beg === -1 ) { beg = 0; } - let end = content.indexOf('\n', pos); - if ( end === -1 ) { end = content.length; } - pos = end; - const fargs = JSON.parse(content.slice(beg, end)); - const filterType = fargs[0]; - - // https://github.com/gorhill/uBlock/issues/2763 - if ( filterType >= 0 && filterType <= 5 && details.ignoreGeneric ) { - continue; - } - - // Do not confuse cosmetic filters with HTML ones. - if ( (filterType === 64) !== isHtmlFilter ) { continue; } - - switch ( filterType ) { - // Lowly generic cosmetic filters - case 0: // simple id-based - if ( exception ) { break; } - if ( fargs[1] !== selector.slice(1) ) { break; } - if ( selector.charAt(0) !== '#' ) { break; } - found = prefix + selector; - break; - case 2: // simple class-based - if ( exception ) { break; } - if ( fargs[1] !== selector.slice(1) ) { break; } - if ( selector.charAt(0) !== '.' ) { break; } - found = prefix + selector; - break; - case 1: // complex id-based - case 3: // complex class-based - if ( exception ) { break; } - if ( fargs[2] !== selector ) { break; } - found = prefix + selector; - break; - // Highly generic cosmetic filters - case 4: // simple highly generic - case 5: // complex highly generic - if ( exception ) { break; } - if ( fargs[1] !== selector ) { break; } - found = prefix + selector; - break; - // Specific cosmetic filtering - // Generic exception - case 8: - // HTML filtering - case 64: - if ( exception !== ((fargs[2] & 0b001) !== 0) ) { break; } - isProcedural = (fargs[2] & 0b010) !== 0; - if ( - isProcedural === false && fargs[3] !== selector || - isProcedural && JSON.parse(fargs[3]).raw !== selector - ) { - break; - } - if ( hostnameMatches(fargs[1]) === false ) { break; } - // https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/ - // Ignore match if specific cosmetic filters are disabled - if ( - filterType === 8 && - exception === false && - details.ignoreSpecific - ) { - break; - } - found = fargs[1] + prefix + selector; - break; - // Scriptlet injection - case 32: - if ( exception !== ((fargs[2] & 0b001) !== 0) ) { break; } - if ( fargs[3] !== selector ) { break; } - if ( hostnameMatches(fargs[1]) ) { - found = fargs[1] + prefix + selector; - } - break; - } - if ( found !== undefined ) { - if ( response[found] === undefined ) { - response[found] = []; - } - response[found].push({ - assetKey: assetKey, - title: entry.title, - supportURL: entry.supportURL - }); - break; - } - } - } - - postMessage({ - id: details.id, - response: response - }); -}; - -/******************************************************************************/ - -onmessage = function(e) { // jshint ignore:line - const msg = e.data; - - switch ( msg.what ) { - case 'resetLists': - listEntries = Object.create(null); - break; - - case 'setList': - listEntries[msg.details.assetKey] = msg.details; - break; - - case 'fromNetFilter': - fromNetFilter(msg); - break; - - case 'fromCosmeticFilter': - fromCosmeticFilter(msg); - break; - } -}; - -/******************************************************************************/ diff --git a/src/js/reverselookup.js b/src/js/reverselookup.js index 8be351967..7d9af5a9a 100644 --- a/src/js/reverselookup.js +++ b/src/js/reverselookup.js @@ -23,198 +23,462 @@ /******************************************************************************/ -µBlock.staticFilteringReverseLookup = (( ) => { +(( ) => { +// >>>>> start of local scope /******************************************************************************/ -const workerTTL = 5 * 60 * 1000; -const pendingResponses = new Map(); +// Worker context -let worker = null; -let workerTTLTimer; -let needLists = true; -let messageId = 1; +if ( + self.WorkerGlobalScope instanceof Object && + self instanceof self.WorkerGlobalScope +) { + const reBlockStart = /^#block-start-(\d+)\n/gm; + let listEntries = Object.create(null); -/******************************************************************************/ - -const onWorkerMessage = function(e) { - const msg = e.data; - const resolver = pendingResponses.get(msg.id); - pendingResponses.delete(msg.id); - resolver(msg.response); -}; - -/******************************************************************************/ - -const stopWorker = function() { - if ( workerTTLTimer !== undefined ) { - clearTimeout(workerTTLTimer); - workerTTLTimer = undefined; - } - if ( worker === null ) { return; } - worker.terminate(); - worker = null; - needLists = true; - for ( const resolver of pendingResponses.values() ) { - resolver(); - } - pendingResponses.clear(); -}; - -/******************************************************************************/ - -const initWorker = function() { - if ( worker === null ) { - worker = new Worker('js/reverselookup-worker.js'); - worker.onmessage = onWorkerMessage; - } - - // The worker will be shutdown after n minutes without being used. - if ( workerTTLTimer !== undefined ) { - clearTimeout(workerTTLTimer); - } - workerTTLTimer = vAPI.setTimeout(stopWorker, workerTTL); - - if ( needLists === false ) { - return Promise.resolve(); - } - needLists = false; - - const entries = new Map(); - - const onListLoaded = function(details) { - const entry = entries.get(details.assetKey); - - // https://github.com/gorhill/uBlock/issues/536 - // Use assetKey when there is no filter list title. - - worker.postMessage({ - what: 'setList', - details: { - assetKey: details.assetKey, - title: entry.title || details.assetKey, - supportURL: entry.supportURL, - content: details.content + const extractBlocks = function(content, begId, endId) { + reBlockStart.lastIndex = 0; + const out = []; + let match = reBlockStart.exec(content); + while ( match !== null ) { + const beg = match.index + match[0].length; + const blockId = parseInt(match[1], 10); + if ( blockId >= begId && blockId < endId ) { + const end = content.indexOf('#block-end-' + match[1], beg); + out.push(content.slice(beg, end)); + reBlockStart.lastIndex = end; } + match = reBlockStart.exec(content); + } + return out.join('\n'); + }; + + // https://github.com/MajkiIT/polish-ads-filter/issues/14768#issuecomment-536006312 + // Avoid reporting badfilter-ed filters. + + const fromNetFilter = function(details) { + const lists = []; + const compiledFilter = details.compiledFilter; + + for ( const assetKey in listEntries ) { + const entry = listEntries[assetKey]; + if ( entry === undefined ) { continue; } + const content = extractBlocks(entry.content, 0, 1); + let pos = 0; + for (;;) { + pos = content.indexOf(compiledFilter, pos); + if ( pos === -1 ) { break; } + // We need an exact match. + // https://github.com/gorhill/uBlock/issues/1392 + // https://github.com/gorhill/uBlock/issues/835 + const notFound = pos !== 0 && + content.charCodeAt(pos - 1) !== 0x0A; + pos += compiledFilter.length; + if ( + notFound || + pos !== content.length && content.charCodeAt(pos) !== 0x0A + ) { + continue; + } + lists.push({ + assetKey: assetKey, + title: entry.title, + supportURL: entry.supportURL + }); + break; + } + } + + const response = {}; + response[details.rawFilter] = lists; + + self.postMessage({ id: details.id, response }); + }; + + // Looking up filter lists from a cosmetic filter is a bit more complicated + // than with network filters: + // + // The filter is its raw representation, not its compiled version. This is + // because the cosmetic filtering engine can't translate a live cosmetic + // filter into its compiled version. Reason is I do not want to burden + // cosmetic filtering with the resource overhead of being able to recompile + // live cosmetic filters. I want the cosmetic filtering code to be left + // completely unaffected by reverse lookup requirements. + // + // Mainly, given a CSS selector and a hostname as context, we will derive + // various versions of compiled filters and see if there are matches. This + // way the whole CPU cost is incurred by the reverse lookup code -- in a + // worker thread, and the cosmetic filtering engine incurs no cost at all. + // + // For this though, the reverse lookup code here needs some knowledge of + // the inners of the cosmetic filtering engine. + // FilterContainer.fromCompiledContent() is our reference code to create + // the various compiled versions. + + const fromCosmeticFilter = function(details) { + const match = /^#@?#\^?/.exec(details.rawFilter); + const prefix = match[0]; + const exception = prefix.charAt(1) === '@'; + const selector = details.rawFilter.slice(prefix.length); + const isHtmlFilter = prefix.endsWith('^'); + const hostname = details.hostname; + + // The longer the needle, the lower the number of false positives. + // https://github.com/uBlockOrigin/uBlock-issues/issues/1139 + // Mind that there is no guarantee a selector has `\w` characters. + const needle = selector.match(/\w+|\*/g).reduce(function(a, b) { + return a.length > b.length ? a : b; + }); + + const regexFromLabels = (prefix, hn, suffix) => + new RegExp( + prefix + + hn.split('.').reduce((acc, item) => `(${acc}\\.)?${item}`) + + suffix + ); + + // https://github.com/uBlockOrigin/uBlock-issues/issues/803 + // Support looking up selectors of the form `*##...` + const reHostname = regexFromLabels('^', hostname, '$'); + let reEntity; + { + const domain = details.domain; + const pos = domain.indexOf('.'); + if ( pos !== -1 ) { + reEntity = regexFromLabels( + '^(', + hostname.slice(0, pos + hostname.length - domain.length), + '\\.)?\\*$' + ); + } + } + + const hostnameMatches = hn => { + return hn === '' || + reHostname.test(hn) || + reEntity !== undefined && reEntity.test(hn); + }; + + const response = Object.create(null); + + for ( const assetKey in listEntries ) { + const entry = listEntries[assetKey]; + if ( entry === undefined ) { continue; } + let content = extractBlocks(entry.content, 1000, 2000), + isProcedural, + found; + let pos = 0; + while ( (pos = content.indexOf(needle, pos)) !== -1 ) { + let beg = content.lastIndexOf('\n', pos); + if ( beg === -1 ) { beg = 0; } + let end = content.indexOf('\n', pos); + if ( end === -1 ) { end = content.length; } + pos = end; + const fargs = JSON.parse(content.slice(beg, end)); + const filterType = fargs[0]; + + // https://github.com/gorhill/uBlock/issues/2763 + if ( + filterType >= 0 && + filterType <= 5 && + details.ignoreGeneric + ) { + continue; + } + + // Do not confuse cosmetic filters with HTML ones. + if ( (filterType === 64) !== isHtmlFilter ) { continue; } + + switch ( filterType ) { + // Lowly generic cosmetic filters + case 0: // simple id-based + if ( exception ) { break; } + if ( fargs[1] !== selector.slice(1) ) { break; } + if ( selector.charAt(0) !== '#' ) { break; } + found = prefix + selector; + break; + case 2: // simple class-based + if ( exception ) { break; } + if ( fargs[1] !== selector.slice(1) ) { break; } + if ( selector.charAt(0) !== '.' ) { break; } + found = prefix + selector; + break; + case 1: // complex id-based + case 3: // complex class-based + if ( exception ) { break; } + if ( fargs[2] !== selector ) { break; } + found = prefix + selector; + break; + // Highly generic cosmetic filters + case 4: // simple highly generic + case 5: // complex highly generic + if ( exception ) { break; } + if ( fargs[1] !== selector ) { break; } + found = prefix + selector; + break; + // Specific cosmetic filtering + // Generic exception + case 8: + // HTML filtering + case 64: + if ( exception !== ((fargs[2] & 0b001) !== 0) ) { break; } + isProcedural = (fargs[2] & 0b010) !== 0; + if ( + isProcedural === false && fargs[3] !== selector || + isProcedural && JSON.parse(fargs[3]).raw !== selector + ) { + break; + } + if ( hostnameMatches(fargs[1]) === false ) { break; } + // https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/ + // Ignore match if specific cosmetic filters are disabled + if ( + filterType === 8 && + exception === false && + details.ignoreSpecific + ) { + break; + } + found = fargs[1] + prefix + selector; + break; + // Scriptlet injection + case 32: + if ( exception !== ((fargs[2] & 0b001) !== 0) ) { break; } + if ( fargs[3] !== selector ) { break; } + if ( hostnameMatches(fargs[1]) ) { + found = fargs[1] + prefix + selector; + } + break; + } + if ( found !== undefined ) { + if ( response[found] === undefined ) { + response[found] = []; + } + response[found].push({ + assetKey: assetKey, + title: entry.title, + supportURL: entry.supportURL + }); + break; + } + } + } + + self.postMessage({ id: details.id, response }); + }; + + self.onmessage = function(e) { // jshint ignore:line + const msg = e.data; + + switch ( msg.what ) { + case 'resetLists': + listEntries = Object.create(null); + break; + + case 'setList': + listEntries[msg.details.assetKey] = msg.details; + break; + + case 'fromNetFilter': + fromNetFilter(msg); + break; + + case 'fromCosmeticFilter': + fromCosmeticFilter(msg); + break; + } + }; + + return; +} + +/******************************************************************************/ + +// Main context + +{ + if ( typeof µBlock !== 'object' ) { return; } + + const workerTTL = 5 * 60 * 1000; + const pendingResponses = new Map(); + + let worker = null; + let workerTTLTimer; + let needLists = true; + let messageId = 1; + + const onWorkerMessage = function(e) { + const msg = e.data; + const resolver = pendingResponses.get(msg.id); + pendingResponses.delete(msg.id); + resolver(msg.response); + }; + + const stopWorker = function() { + if ( workerTTLTimer !== undefined ) { + clearTimeout(workerTTLTimer); + workerTTLTimer = undefined; + } + if ( worker === null ) { return; } + worker.terminate(); + worker = null; + needLists = true; + for ( const resolver of pendingResponses.values() ) { + resolver(); + } + pendingResponses.clear(); + }; + + const initWorker = function() { + if ( worker === null ) { + worker = new Worker('js/reverselookup.js'); + worker.onmessage = onWorkerMessage; + } + + // The worker will be shutdown after n minutes without being used. + if ( workerTTLTimer !== undefined ) { + clearTimeout(workerTTLTimer); + } + workerTTLTimer = vAPI.setTimeout(stopWorker, workerTTL); + + if ( needLists === false ) { + return Promise.resolve(); + } + needLists = false; + + const entries = new Map(); + + const onListLoaded = function(details) { + const entry = entries.get(details.assetKey); + + // https://github.com/gorhill/uBlock/issues/536 + // Use assetKey when there is no filter list title. + + worker.postMessage({ + what: 'setList', + details: { + assetKey: details.assetKey, + title: entry.title || details.assetKey, + supportURL: entry.supportURL, + content: details.content + } + }); + }; + + const µb = µBlock; + for ( const listKey in µb.availableFilterLists ) { + if ( µb.availableFilterLists.hasOwnProperty(listKey) === false ) { + continue; + } + const entry = µb.availableFilterLists[listKey]; + if ( entry.off === true ) { continue; } + entries.set(listKey, { + title: listKey !== µb.userFiltersPath ? + entry.title : + vAPI.i18n('1pPageName'), + supportURL: entry.supportURL || '' + }); + } + if ( entries.size === 0 ) { + return Promise.resolve(); + } + + const promises = []; + for ( const listKey of entries.keys() ) { + promises.push( + µb.getCompiledFilterList(listKey).then(details => { + onListLoaded(details); + }) + ); + } + return Promise.all(promises); + }; + + const fromNetFilter = async function(rawFilter) { + if ( typeof rawFilter !== 'string' || rawFilter === '' ) { return; } + + const µb = µBlock; + const writer = new µb.CompiledLineIO.Writer(); + const parser = new vAPI.StaticFilteringParser(); + parser.setMaxTokenLength(µb.urlTokenizer.MAX_TOKEN_LENGTH); + parser.analyze(rawFilter); + + if ( µb.staticNetFilteringEngine.compile(parser, writer) === false ) { + return; + } + + await initWorker(); + + const id = messageId++; + worker.postMessage({ + what: 'fromNetFilter', + id: id, + compiledFilter: writer.last(), + rawFilter: rawFilter + }); + + return new Promise(resolve => { + pendingResponses.set(id, resolve); }); }; - const µb = µBlock; - for ( const listKey in µb.availableFilterLists ) { - if ( µb.availableFilterLists.hasOwnProperty(listKey) === false ) { - continue; + const fromCosmeticFilter = async function(details) { + if ( + typeof details.rawFilter !== 'string' || + details.rawFilter === '' + ) { + return; } - const entry = µb.availableFilterLists[listKey]; - if ( entry.off === true ) { continue; } - entries.set(listKey, { - title: listKey !== µb.userFiltersPath ? - entry.title : - vAPI.i18n('1pPageName'), - supportURL: entry.supportURL || '' + + await initWorker(); + + const id = messageId++; + const hostname = µBlock.URI.hostnameFromURI(details.url); + + worker.postMessage({ + what: 'fromCosmeticFilter', + id: id, + domain: µBlock.URI.domainFromHostname(hostname), + hostname: hostname, + ignoreGeneric: + µBlock.staticNetFilteringEngine.matchStringReverse( + 'generichide', + details.url + ) === 2, + ignoreSpecific: + µBlock.staticNetFilteringEngine.matchStringReverse( + 'specifichide', + details.url + ) === 2, + rawFilter: details.rawFilter }); - } - if ( entries.size === 0 ) { - return Promise.resolve(); - } - const promises = []; - for ( const listKey of entries.keys() ) { - promises.push( - µb.getCompiledFilterList(listKey).then(details => { - onListLoaded(details); - }) - ); - } - return Promise.all(promises); -}; - -/******************************************************************************/ - -const fromNetFilter = async function(rawFilter) { - if ( typeof rawFilter !== 'string' || rawFilter === '' ) { return; } - - const µb = µBlock; - const writer = new µb.CompiledLineIO.Writer(); - const parser = new vAPI.StaticFilteringParser(); - parser.setMaxTokenLength(µb.urlTokenizer.MAX_TOKEN_LENGTH); - parser.analyze(rawFilter); - - if ( µb.staticNetFilteringEngine.compile(parser, writer) === false ) { - return; - } - - await initWorker(); - - const id = messageId++; - worker.postMessage({ - what: 'fromNetFilter', - id: id, - compiledFilter: writer.last(), - rawFilter: rawFilter - }); - - return new Promise(resolve => { - pendingResponses.set(id, resolve); - }); -}; - -/******************************************************************************/ - -const fromCosmeticFilter = async function(details) { - if ( typeof details.rawFilter !== 'string' || details.rawFilter === '' ) { - return; - } - - await initWorker(); - - const id = messageId++; - const hostname = µBlock.URI.hostnameFromURI(details.url); - - worker.postMessage({ - what: 'fromCosmeticFilter', - id: id, - domain: µBlock.URI.domainFromHostname(hostname), - hostname: hostname, - ignoreGeneric: - µBlock.staticNetFilteringEngine.matchStringReverse( - 'generichide', - details.url - ) === 2, - ignoreSpecific: - µBlock.staticNetFilteringEngine.matchStringReverse( - 'specifichide', - details.url - ) === 2, - rawFilter: details.rawFilter - }); - - return new Promise(resolve => { - pendingResponses.set(id, resolve); - }); - -}; - -/******************************************************************************/ - -// This tells the worker that filter lists may have changed. - -const resetLists = function() { - needLists = true; - if ( worker === null ) { return; } - worker.postMessage({ what: 'resetLists' }); -}; - -/******************************************************************************/ - -return { - fromNetFilter, - fromCosmeticFilter, - resetLists, - shutdown: stopWorker -}; + return new Promise(resolve => { + pendingResponses.set(id, resolve); + }); + + }; + + // This tells the worker that filter lists may have changed. + + const resetLists = function() { + needLists = true; + if ( worker === null ) { return; } + worker.postMessage({ what: 'resetLists' }); + }; + + µBlock.staticFilteringReverseLookup = { + fromNetFilter, + fromCosmeticFilter, + resetLists, + shutdown: stopWorker + }; +} /******************************************************************************/ +// <<<<< end of local scope })(); /******************************************************************************/