/******************************************************************************* 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 */ 'use strict'; /******************************************************************************/ import staticNetFilteringEngine from './static-net-filtering.js'; import µb from './background.js'; import { CompiledListWriter } from './static-filtering-io.js'; import { i18n$ } from './i18n.js'; import * as sfp from './static-filtering-parser.js'; import { domainFromHostname, hostnameFromURI, } from './uri-utils.js'; /******************************************************************************/ const pendingResponses = new Map(); let worker = null; 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() { workerTTLTimer.off(); if ( worker === null ) { return; } worker.terminate(); worker = null; needLists = true; for ( const resolver of pendingResponses.values() ) { resolver(); } pendingResponses.clear(); }; const workerTTLTimer = vAPI.defer.create(stopWorker); const workerTTL = { min: 5 }; 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. workerTTLTimer.offon(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 } }); }; 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 : 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 writer = new CompiledListWriter(); const parser = new sfp.AstFilterParser({ expertMode: true, maxTokenLength: staticNetFilteringEngine.MAX_TOKEN_LENGTH, nativeCssHas: vAPI.webextFlavor.env.includes('native_css_has'), }); parser.parse(rawFilter); const compiler = staticNetFilteringEngine.createCompiler(); if ( compiler.compile(parser, writer) === false ) { return; } await initWorker(); const id = messageId++; worker.postMessage({ what: 'fromNetFilter', id, compiledFilter: writer.last(), rawFilter, }); return new Promise(resolve => { pendingResponses.set(id, resolve); }); }; const fromExtendedFilter = async function(details) { if ( typeof details.rawFilter !== 'string' || details.rawFilter === '' ) { return; } await initWorker(); const id = messageId++; const hostname = hostnameFromURI(details.url); const parser = new sfp.AstFilterParser({ expertMode: true, nativeCssHas: vAPI.webextFlavor.env.includes('native_css_has'), }); parser.parse(details.rawFilter); let compiled; if ( parser.isScriptletFilter() ) { compiled = JSON.stringify(parser.getScriptletArgs()); } worker.postMessage({ what: 'fromExtendedFilter', id, domain: domainFromHostname(hostname), hostname, ignoreGeneric: staticNetFilteringEngine.matchRequestReverse( 'generichide', details.url ) === 2, ignoreSpecific: staticNetFilteringEngine.matchRequestReverse( 'specifichide', details.url ) === 2, rawFilter: details.rawFilter, compiled, }); 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' }); }; /******************************************************************************/ const staticFilteringReverseLookup = { fromNetFilter, fromExtendedFilter, resetLists, shutdown: stopWorker }; export default staticFilteringReverseLookup; /******************************************************************************/