diff --git a/platform/nodejs/main.js b/platform/nodejs/main.js index 41a6f809d..6874bdf94 100644 --- a/platform/nodejs/main.js +++ b/platform/nodejs/main.js @@ -29,10 +29,10 @@ import './lib/punycode.js'; import './lib/publicsuffixlist/publicsuffixlist.js'; import globals from './js/globals.js'; +import snfe from './js/static-net-filtering.js'; import { FilteringContext } from './js/filtering-context.js'; import { LineIterator } from './js/text-iterators.js'; import { StaticFilteringParser } from './js/static-filtering-parser.js'; -import { staticNetFilteringEngine } from './js/static-net-filtering.js'; import { CompiledListReader, @@ -41,33 +41,31 @@ import { /******************************************************************************/ -function compileList(rawText, writer) { +function compileList(rawText, writer, options = {}) { const lineIter = new LineIterator(rawText); const parser = new StaticFilteringParser(true); + const events = Array.isArray(options.events) ? options.events : undefined; - parser.setMaxTokenLength(staticNetFilteringEngine.MAX_TOKEN_LENGTH); + parser.setMaxTokenLength(snfe.MAX_TOKEN_LENGTH); while ( lineIter.eot() === false ) { let line = lineIter.next(); - while ( line.endsWith(' \\') ) { if ( lineIter.peek(4) !== ' ' ) { break; } line = line.slice(0, -2).trim() + lineIter.next().trim(); } parser.analyze(line); - if ( parser.shouldIgnore() ) { continue; } if ( parser.category !== parser.CATStaticNetFilter ) { continue; } if ( parser.patternHasUnicode() && parser.toASCII() === false ) { continue; } - if ( staticNetFilteringEngine.compile(parser, writer) ) { continue; } - if ( staticNetFilteringEngine.error !== undefined ) { - console.info(JSON.stringify({ - realm: 'message', + if ( snfe.compile(parser, writer) ) { continue; } + if ( snfe.error !== undefined && events !== undefined ) { + options.events.push({ type: 'error', - text: staticNetFilteringEngine.error - })); + text: snfe.error + }); } } @@ -79,13 +77,13 @@ function applyList(name, raw) { writer.properties.set('name', name); const compiled = compileList(raw, writer); const reader = new CompiledListReader(compiled); - staticNetFilteringEngine.fromCompiled(reader); + snfe.fromCompiled(reader); } function enableWASM(path) { return Promise.all([ globals.publicSuffixList.enableWASM(`${path}/lib/publicsuffixlist`), - staticNetFilteringEngine.enableWASM(`${path}/js`), + snfe.enableWASM(`${path}/js`), ]); } @@ -94,35 +92,32 @@ function pslInit(raw) { const require = createRequire(import.meta.url); // jshint ignore:line raw = require('./data/effective_tld_names.json'); if ( typeof raw !== 'string' || raw.trim() === '' ) { - console.info('Unable to populate public suffix list'); + console.error('Unable to populate public suffix list'); return; } } globals.publicSuffixList.parse(raw, globals.punycode.toASCII); - console.info('Public suffix list populated'); } -function restart(lists) { +function restart(lists, options = {}) { // Remove all filters reset(); if ( Array.isArray(lists) && lists.length !== 0 ) { // Populate filtering engine with filter lists for ( const { name, raw } of lists ) { - applyList(name, raw); + applyList(name, raw, options); } // Commit changes - staticNetFilteringEngine.freeze(); - staticNetFilteringEngine.optimize(); + snfe.freeze(); + snfe.optimize(); } - console.info('Static network filtering engine populated'); - - return staticNetFilteringEngine; + return snfe; } function reset() { - staticNetFilteringEngine.reset(); + snfe.reset(); } export { diff --git a/platform/nodejs/test.js b/platform/nodejs/test.js index b0f7966e8..c4735e1bc 100644 --- a/platform/nodejs/test.js +++ b/platform/nodejs/test.js @@ -51,7 +51,7 @@ function fetch(listName) { } */ - pslInit(); + await pslInit(); const snfe = await Promise.all([ fetch('easylist'), diff --git a/src/background.html b/src/background.html index 21429406f..9f085da94 100644 --- a/src/background.html +++ b/src/background.html @@ -5,37 +5,17 @@ uBlock Origin - - - - - - - - - - - - - - - - - - - - diff --git a/src/js/assets.js b/src/js/assets.js index d7e144e55..3394e8a82 100644 --- a/src/js/assets.js +++ b/src/js/assets.js @@ -23,7 +23,9 @@ /******************************************************************************/ -import µBlock from './background.js'; +import cacheStorage from './cachestorage.js'; +import logger from './logger.js'; +import µb from './background.js'; /******************************************************************************/ @@ -31,7 +33,7 @@ const reIsExternalPath = /^(?:[a-z-]+):\/\//; const reIsUserAsset = /^user-/; const errorCantConnectTo = vAPI.i18n('errorCantConnectTo'); -const api = {}; +const assets = {}; // A hint for various pieces of code to take measures if possible to save // bandwidth of remote servers. @@ -41,13 +43,13 @@ let remoteServerFriendly = false; const observers = []; -api.addObserver = function(observer) { +assets.addObserver = function(observer) { if ( observers.indexOf(observer) === -1 ) { observers.push(observer); } }; -api.removeObserver = function(observer) { +assets.removeObserver = function(observer) { let pos; while ( (pos = observers.indexOf(observer)) !== -1 ) { observers.splice(pos, 1); @@ -65,11 +67,11 @@ const fireNotification = function(topic, details) { /******************************************************************************/ -api.fetch = function(url, options = {}) { +assets.fetch = function(url, options = {}) { return new Promise((resolve, reject) => { // Start of executor - const timeoutAfter = µBlock.hiddenSettings.assetFetchTimeout * 1000 || 30000; + const timeoutAfter = µb.hiddenSettings.assetFetchTimeout * 1000 || 30000; const xhr = new XMLHttpRequest(); let contentLoaded = 0; let timeoutTimer; @@ -86,7 +88,7 @@ api.fetch = function(url, options = {}) { }; const fail = function(details, msg) { - µBlock.logger.writeOne({ + logger.writeOne({ realm: 'message', type: 'error', text: msg, @@ -154,7 +156,7 @@ api.fetch = function(url, options = {}) { /******************************************************************************/ -api.fetchText = async function(url) { +assets.fetchText = async function(url) { const isExternal = reIsExternalPath.test(url); let actualUrl = isExternal ? url : vAPI.getURL(url); @@ -171,7 +173,7 @@ api.fetchText = async function(url) { // servers. if ( isExternal && remoteServerFriendly !== true ) { const cacheBypassToken = - µBlock.hiddenSettings.updateAssetBypassBrowserCache + µb.hiddenSettings.updateAssetBypassBrowserCache ? Math.floor(Date.now() / 1000) % 86413 : Math.floor(Date.now() / 3600000) % 13; const queryValue = `_=${cacheBypassToken}`; @@ -185,7 +187,7 @@ api.fetchText = async function(url) { let details = { content: '' }; try { - details = await api.fetch(actualUrl); + details = await assets.fetch(actualUrl); // Consider an empty result to be an error if ( stringIsNotEmpty(details.content) === false ) { @@ -223,7 +225,7 @@ api.fetchText = async function(url) { // https://github.com/gorhill/uBlock/issues/3331 // Support the seamless loading of sublists. -api.fetchFilterList = async function(mainlistURL) { +assets.fetchFilterList = async function(mainlistURL) { const toParsedURL = url => { try { return new URL(url.trim()); @@ -265,7 +267,7 @@ api.fetchFilterList = async function(mainlistURL) { } if ( result instanceof Object === false ) { continue; } const content = result.content; - const slices = µBlock.preparseDirectives.split(content); + const slices = µb.preparseDirectives.split(content); for ( let i = 0, n = slices.length - 1; i < n; i++ ) { const slice = content.slice(slices[i+0], slices[i+1]); if ( (i & 1) !== 0 ) { @@ -288,7 +290,7 @@ api.fetchFilterList = async function(mainlistURL) { out.push( slice.slice(lastIndex, match.index + match[0].length), `! >>>>>>>> ${subURL}\n`, - api.fetchText(subURL), + assets.fetchText(subURL), `! <<<<<<<< ${subURL}\n` ); lastIndex = reInclude.lastIndex; @@ -346,7 +348,7 @@ let assetSourceRegistryPromise, const getAssetSourceRegistry = function() { if ( assetSourceRegistryPromise === undefined ) { - assetSourceRegistryPromise = µBlock.cacheStorage.get( + assetSourceRegistryPromise = cacheStorage.get( 'assetSourceRegistry' ).then(bin => { if ( @@ -356,12 +358,12 @@ const getAssetSourceRegistry = function() { assetSourceRegistry = bin.assetSourceRegistry; return assetSourceRegistry; } - return api.fetchText( - µBlock.assetsBootstrapLocation || 'assets/assets.json' + return assets.fetchText( + µb.assetsBootstrapLocation || 'assets/assets.json' ).then(details => { return details.content !== '' ? details - : api.fetchText('assets/assets.json'); + : assets.fetchText('assets/assets.json'); }).then(details => { updateAssetSourceRegistry(details.content, true); return assetSourceRegistry; @@ -418,7 +420,7 @@ const saveAssetSourceRegistry = (( ) => { let timer; const save = function() { timer = undefined; - µBlock.cacheStorage.set({ assetSourceRegistry }); + cacheStorage.set({ assetSourceRegistry }); }; return function(lazily) { if ( timer !== undefined ) { @@ -464,13 +466,13 @@ const updateAssetSourceRegistry = function(json, silent) { saveAssetSourceRegistry(); }; -api.registerAssetSource = async function(assetKey, details) { +assets.registerAssetSource = async function(assetKey, details) { await getAssetSourceRegistry(); registerAssetSource(assetKey, details); saveAssetSourceRegistry(true); }; -api.unregisterAssetSource = async function(assetKey) { +assets.unregisterAssetSource = async function(assetKey) { await getAssetSourceRegistry(); unregisterAssetSource(assetKey); saveAssetSourceRegistry(true); @@ -489,7 +491,7 @@ let assetCacheRegistry = {}; const getAssetCacheRegistry = function() { if ( assetCacheRegistryPromise === undefined ) { - assetCacheRegistryPromise = µBlock.cacheStorage.get( + assetCacheRegistryPromise = cacheStorage.get( 'assetCacheRegistry' ).then(bin => { if ( @@ -523,7 +525,7 @@ const saveAssetCacheRegistry = (( ) => { let timer; const save = function() { timer = undefined; - µBlock.cacheStorage.set({ assetCacheRegistry }); + cacheStorage.set({ assetCacheRegistry }); }; return function(lazily) { if ( timer !== undefined ) { clearTimeout(timer); } @@ -547,7 +549,7 @@ const assetCacheRead = async function(assetKey, updateReadTime = false) { const [ , bin ] = await Promise.all([ getAssetCacheRegistry(), - µBlock.cacheStorage.get(internalKey), + cacheStorage.get(internalKey), ]); if ( bin instanceof Object === false || @@ -593,7 +595,7 @@ const assetCacheWrite = async function(assetKey, details) { if ( typeof options.url === 'string' ) { entry.remoteURL = options.url; } - µBlock.cacheStorage.set({ + cacheStorage.set({ assetCacheRegistry, [`cache/${assetKey}`]: content }); @@ -623,8 +625,8 @@ const assetCacheRemove = async function(pattern) { } if ( removedContent.length !== 0 ) { await Promise.all([ - µBlock.cacheStorage.remove(removedContent), - µBlock.cacheStorage.set({ assetCacheRegistry }), + cacheStorage.remove(removedContent), + cacheStorage.set({ assetCacheRegistry }), ]); } for ( let i = 0; i < removedEntries.length; i++ ) { @@ -658,7 +660,7 @@ const assetCacheMarkAsDirty = async function(pattern, exclude) { mustSave = true; } if ( mustSave ) { - µBlock.cacheStorage.set({ assetCacheRegistry }); + cacheStorage.set({ assetCacheRegistry }); } }; @@ -708,8 +710,8 @@ const saveUserAsset = function(assetKey, content) { /******************************************************************************/ -api.get = async function(assetKey, options = {}) { - if ( assetKey === µBlock.userFiltersPath ) { +assets.get = async function(assetKey, options = {}) { + if ( assetKey === µb.userFiltersPath ) { return readUserAsset(assetKey); } @@ -770,8 +772,8 @@ api.get = async function(assetKey, options = {}) { continue; } const details = assetDetails.content === 'filters' - ? await api.fetchFilterList(contentURL) - : await api.fetchText(contentURL); + ? await assets.fetchFilterList(contentURL) + : await assets.fetchText(contentURL); if ( details.content === '' ) { continue; } if ( reIsExternalPath.test(contentURL) && options.dontCache !== true ) { assetCacheWrite(assetKey, { @@ -832,8 +834,8 @@ const getRemote = async function(assetKey) { if ( reIsExternalPath.test(contentURL) === false ) { continue; } const result = assetDetails.content === 'filters' - ? await api.fetchFilterList(contentURL) - : await api.fetchText(contentURL); + ? await assets.fetchFilterList(contentURL) + : await assets.fetchText(contentURL); // Failure if ( stringIsNotEmpty(result.content) === false ) { @@ -861,7 +863,7 @@ const getRemote = async function(assetKey) { /******************************************************************************/ -api.put = async function(assetKey, content) { +assets.put = async function(assetKey, content) { return reIsUserAsset.test(assetKey) ? await saveUserAsset(assetKey, content) : await assetCacheWrite(assetKey, content); @@ -869,7 +871,7 @@ api.put = async function(assetKey, content) { /******************************************************************************/ -api.metadata = async function() { +assets.metadata = async function() { await Promise.all([ getAssetSourceRegistry(), getAssetCacheRegistry(), @@ -888,7 +890,7 @@ api.metadata = async function() { assetEntry.isDefault = assetEntry.off === undefined || assetEntry.off === true && - µBlock.listMatchesEnvironment(assetEntry); + µb.listMatchesEnvironment(assetEntry); } if ( cacheEntry ) { assetEntry.cached = true; @@ -911,13 +913,13 @@ api.metadata = async function() { /******************************************************************************/ -api.purge = assetCacheMarkAsDirty; +assets.purge = assetCacheMarkAsDirty; -api.remove = function(pattern) { +assets.remove = function(pattern) { return assetCacheRemove(pattern); }; -api.rmrf = function() { +assets.rmrf = function() { return assetCacheRemove(/./); }; @@ -1014,7 +1016,7 @@ const updateDone = function() { fireNotification('after-assets-updated', { assetKeys: assetKeys }); }; -api.updateStart = function(details) { +assets.updateStart = function(details) { const oldUpdateDelay = updaterAssetDelay; const newUpdateDelay = typeof details.delay === 'number' ? details.delay @@ -1031,7 +1033,7 @@ api.updateStart = function(details) { updateFirst(); }; -api.updateStop = function() { +assets.updateStop = function() { if ( updaterTimer ) { clearTimeout(updaterTimer); updaterTimer = undefined; @@ -1041,15 +1043,13 @@ api.updateStop = function() { } }; -api.isUpdating = function() { +assets.isUpdating = function() { return updaterStatus === 'updating' && - updaterAssetDelay <= µBlock.hiddenSettings.manualUpdateAssetFetchPeriod; + updaterAssetDelay <= µb.hiddenSettings.manualUpdateAssetFetchPeriod; }; /******************************************************************************/ -// Export - -µBlock.assets = api; +export default assets; /******************************************************************************/ diff --git a/src/js/background.js b/src/js/background.js index b6794f5ac..347324b4d 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -24,6 +24,8 @@ /******************************************************************************/ import globals from './globals.js'; +import logger from './logger.js'; +import { FilteringContext } from './filtering-context.js'; import { domainFromHostname, @@ -31,11 +33,6 @@ import { originFromURI, } from './uri-utils.js'; -import { FilteringContext } from './filtering-context.js'; -import { CompiledListWriter } from './static-filtering-io.js'; -import { StaticFilteringParser } from './static-filtering-parser.js'; -import { staticNetFilteringEngine } from './static-net-filtering.js'; - /******************************************************************************/ // Not all platforms may have properly declared vAPI.webextFlavor. @@ -333,7 +330,6 @@ const µBlock = { // jshint ignore:line if ( this.tabDomain === undefined ) { void this.getTabDomain(); } - const logger = µBlock.logger; const filters = this.filter; // Many filters may have been applied to the current context if ( Array.isArray(filters) === false ) { @@ -347,9 +343,6 @@ const µBlock = { // jshint ignore:line }; µBlock.filteringContext = new µBlock.FilteringContext(); -µBlock.CompiledListWriter = CompiledListWriter; -µBlock.StaticFilteringParser = StaticFilteringParser; -µBlock.staticNetFilteringEngine = staticNetFilteringEngine; globals.µBlock = µBlock; diff --git a/src/js/cachestorage.js b/src/js/cachestorage.js index 85cd9c0ac..2c2e60957 100644 --- a/src/js/cachestorage.js +++ b/src/js/cachestorage.js @@ -25,7 +25,8 @@ /******************************************************************************/ -import µBlock from './background.js'; +import lz4Codec from './lz4.js'; +import µb from './background.js'; /******************************************************************************/ @@ -54,431 +55,434 @@ import µBlock from './background.js'; // https://github.com/uBlockOrigin/uBlock-issues/issues/409 // Allow forcing the use of webext storage on Firefox. -µBlock.cacheStorage = (function() { +const STORAGE_NAME = 'uBlock0CacheStorage'; - const STORAGE_NAME = 'uBlock0CacheStorage'; +// Default to webext storage. +const storageLocal = webext.storage.local; - // Default to webext storage. - const localStorage = webext.storage.local; - const api = { - name: 'browser.storage.local', - get: localStorage.get, - set: localStorage.set, - remove: localStorage.remove, - clear: localStorage.clear, - getBytesInUse: localStorage.getBytesInUse, - select: function(selectedBackend) { - let actualBackend = selectedBackend; - if ( actualBackend === undefined || actualBackend === 'unset' ) { - actualBackend = vAPI.webextFlavor.soup.has('firefox') - ? 'indexedDB' - : 'browser.storage.local'; - } - if ( actualBackend === 'indexedDB' ) { - return selectIDB().then(success => { - if ( success || selectedBackend === 'indexedDB' ) { - clearWebext(); - return 'indexedDB'; - } - clearIDB(); - return 'browser.storage.local'; - }); - } - if ( actualBackend === 'browser.storage.local' ) { +const cacheStorage = { + name: 'browser.storage.local', + get: storageLocal.get.bind(storageLocal), + set: storageLocal.set.bind(storageLocal), + remove: storageLocal.remove.bind(storageLocal), + clear: storageLocal.clear.bind(storageLocal), + // Not all platforms support getBytesInUse + getBytesInUse: storageLocal.getBytesInUse + ? storageLocal.getBytesInUse.bind(storageLocal) + : undefined, + select: function(selectedBackend) { + let actualBackend = selectedBackend; + if ( actualBackend === undefined || actualBackend === 'unset' ) { + actualBackend = vAPI.webextFlavor.soup.has('firefox') + ? 'indexedDB' + : 'browser.storage.local'; + } + if ( actualBackend === 'indexedDB' ) { + return selectIDB().then(success => { + if ( success || selectedBackend === 'indexedDB' ) { + clearWebext(); + return 'indexedDB'; + } clearIDB(); - } - return Promise.resolve('browser.storage.local'); - - }, - error: undefined + return 'browser.storage.local'; + }); + } + if ( actualBackend === 'browser.storage.local' ) { + clearIDB(); + } + return Promise.resolve('browser.storage.local'); + + }, + error: undefined +}; + +// Reassign API entries to that of indexedDB-based ones +const selectIDB = async function() { + let db; + let dbPromise; + let dbTimer; + + const noopfn = function () { }; - // Reassign API entries to that of indexedDB-based ones - const selectIDB = async function() { - let db; - let dbPromise; - let dbTimer; + const disconnect = function() { + if ( dbTimer !== undefined ) { + clearTimeout(dbTimer); + dbTimer = undefined; + } + if ( db instanceof IDBDatabase ) { + db.close(); + db = undefined; + } + }; - const noopfn = function () { - }; - - const disconnect = function() { - if ( dbTimer !== undefined ) { - clearTimeout(dbTimer); + const keepAlive = function() { + if ( dbTimer !== undefined ) { + clearTimeout(dbTimer); + } + dbTimer = vAPI.setTimeout( + ( ) => { dbTimer = undefined; - } - if ( db instanceof IDBDatabase ) { - db.close(); - db = undefined; - } - }; + disconnect(); + }, + Math.max( + µb.hiddenSettings.autoUpdateAssetFetchPeriod * 2 * 1000, + 180000 + ) + ); + }; - const keepAlive = function() { - if ( dbTimer !== undefined ) { - clearTimeout(dbTimer); - } - dbTimer = vAPI.setTimeout( - ( ) => { - dbTimer = undefined; - disconnect(); - }, - Math.max( - µBlock.hiddenSettings.autoUpdateAssetFetchPeriod * 2 * 1000, - 180000 - ) - ); - }; + // https://github.com/gorhill/uBlock/issues/3156 + // I have observed that no event was fired in Tor Browser 7.0.7 + + // medium security level after the request to open the database was + // created. When this occurs, I have also observed that the `error` + // property was already set, so this means uBO can detect here whether + // the database can be opened successfully. A try-catch block is + // necessary when reading the `error` property because we are not + // allowed to read this propery outside of event handlers in newer + // implementation of IDBRequest (my understanding). - // https://github.com/gorhill/uBlock/issues/3156 - // I have observed that no event was fired in Tor Browser 7.0.7 + - // medium security level after the request to open the database was - // created. When this occurs, I have also observed that the `error` - // property was already set, so this means uBO can detect here whether - // the database can be opened successfully. A try-catch block is - // necessary when reading the `error` property because we are not - // allowed to read this propery outside of event handlers in newer - // implementation of IDBRequest (my understanding). - - const getDb = function() { - keepAlive(); - if ( db !== undefined ) { - return Promise.resolve(db); - } - if ( dbPromise !== undefined ) { - return dbPromise; - } - dbPromise = new Promise(resolve => { - let req; - try { - req = indexedDB.open(STORAGE_NAME, 1); - if ( req.error ) { - console.log(req.error); - req = undefined; - } - } catch(ex) { - } - if ( req === undefined ) { - db = null; - dbPromise = undefined; - return resolve(null); - } - req.onupgradeneeded = function(ev) { - if ( ev.oldVersion === 1 ) { return; } - try { - const db = ev.target.result; - db.createObjectStore(STORAGE_NAME, { keyPath: 'key' }); - } catch(ex) { - req.onerror(); - } - }; - req.onsuccess = function(ev) { - if ( resolve === undefined ) { return; } - req = undefined; - db = ev.target.result; - dbPromise = undefined; - resolve(db); - resolve = undefined; - }; - req.onerror = req.onblocked = function() { - if ( resolve === undefined ) { return; } - req = undefined; - console.log(this.error); - db = null; - dbPromise = undefined; - resolve(null); - resolve = undefined; - }; - setTimeout(( ) => { - if ( resolve === undefined ) { return; } - db = null; - dbPromise = undefined; - resolve(null); - resolve = undefined; - }, 5000); - }); + const getDb = function() { + keepAlive(); + if ( db !== undefined ) { + return Promise.resolve(db); + } + if ( dbPromise !== undefined ) { return dbPromise; - }; - - const fromBlob = function(data) { - if ( data instanceof Blob === false ) { - return Promise.resolve(data); - } - return new Promise(resolve => { - const blobReader = new FileReader(); - blobReader.onloadend = ev => { - resolve(new Uint8Array(ev.target.result)); - }; - blobReader.readAsArrayBuffer(data); - }); - }; - - const toBlob = function(data) { - const value = data instanceof Uint8Array - ? new Blob([ data ]) - : data; - return Promise.resolve(value); - }; - - const compress = function(store, key, data) { - return µBlock.lz4Codec.encode(data, toBlob).then(value => { - store.push({ key, value }); - }); - }; - - const decompress = function(store, key, data) { - return µBlock.lz4Codec.decode(data, fromBlob).then(data => { - store[key] = data; - }); - }; - - const getFromDb = async function(keys, keyvalStore, callback) { - if ( typeof callback !== 'function' ) { return; } - if ( keys.length === 0 ) { return callback(keyvalStore); } - const promises = []; - const gotOne = function() { - if ( typeof this.result !== 'object' ) { return; } - const { key, value } = this.result; - keyvalStore[key] = value; - if ( value instanceof Blob === false ) { return; } - promises.push(decompress(keyvalStore, key, value)); - }; + } + dbPromise = new Promise(resolve => { + let req; try { - const db = await getDb(); - if ( !db ) { return callback(); } - const transaction = db.transaction(STORAGE_NAME, 'readonly'); - transaction.oncomplete = - transaction.onerror = - transaction.onabort = ( ) => { - Promise.all(promises).then(( ) => { - callback(keyvalStore); - }); - }; - const table = transaction.objectStore(STORAGE_NAME); - for ( const key of keys ) { - const req = table.get(key); - req.onsuccess = gotOne; - req.onerror = noopfn; + req = indexedDB.open(STORAGE_NAME, 1); + if ( req.error ) { + console.log(req.error); + req = undefined; } + } catch(ex) { } - catch(reason) { - console.info(`cacheStorage.getFromDb() failed: ${reason}`); - callback(); + if ( req === undefined ) { + db = null; + dbPromise = undefined; + return resolve(null); } - }; + req.onupgradeneeded = function(ev) { + if ( ev.oldVersion === 1 ) { return; } + try { + const db = ev.target.result; + db.createObjectStore(STORAGE_NAME, { keyPath: 'key' }); + } catch(ex) { + req.onerror(); + } + }; + req.onsuccess = function(ev) { + if ( resolve === undefined ) { return; } + req = undefined; + db = ev.target.result; + dbPromise = undefined; + resolve(db); + resolve = undefined; + }; + req.onerror = req.onblocked = function() { + if ( resolve === undefined ) { return; } + req = undefined; + console.log(this.error); + db = null; + dbPromise = undefined; + resolve(null); + resolve = undefined; + }; + setTimeout(( ) => { + if ( resolve === undefined ) { return; } + db = null; + dbPromise = undefined; + resolve(null); + resolve = undefined; + }, 5000); + }); + return dbPromise; + }; - const visitAllFromDb = async function(visitFn) { + const fromBlob = function(data) { + if ( data instanceof Blob === false ) { + return Promise.resolve(data); + } + return new Promise(resolve => { + const blobReader = new FileReader(); + blobReader.onloadend = ev => { + resolve(new Uint8Array(ev.target.result)); + }; + blobReader.readAsArrayBuffer(data); + }); + }; + + const toBlob = function(data) { + const value = data instanceof Uint8Array + ? new Blob([ data ]) + : data; + return Promise.resolve(value); + }; + + const compress = function(store, key, data) { + return lz4Codec.encode(data, toBlob).then(value => { + store.push({ key, value }); + }); + }; + + const decompress = function(store, key, data) { + return lz4Codec.decode(data, fromBlob).then(data => { + store[key] = data; + }); + }; + + const getFromDb = async function(keys, keyvalStore, callback) { + if ( typeof callback !== 'function' ) { return; } + if ( keys.length === 0 ) { return callback(keyvalStore); } + const promises = []; + const gotOne = function() { + if ( typeof this.result !== 'object' ) { return; } + const { key, value } = this.result; + keyvalStore[key] = value; + if ( value instanceof Blob === false ) { return; } + promises.push(decompress(keyvalStore, key, value)); + }; + try { const db = await getDb(); - if ( !db ) { return visitFn(); } + if ( !db ) { return callback(); } const transaction = db.transaction(STORAGE_NAME, 'readonly'); transaction.oncomplete = transaction.onerror = - transaction.onabort = ( ) => visitFn(); + transaction.onabort = ( ) => { + Promise.all(promises).then(( ) => { + callback(keyvalStore); + }); + }; const table = transaction.objectStore(STORAGE_NAME); - const req = table.openCursor(); - req.onsuccess = function(ev) { - let cursor = ev.target && ev.target.result; - if ( !cursor ) { return; } - let entry = cursor.value; - visitFn(entry); - cursor.continue(); - }; - }; - - const getAllFromDb = function(callback) { - if ( typeof callback !== 'function' ) { return; } - const promises = []; - const keyvalStore = {}; - visitAllFromDb(entry => { - if ( entry === undefined ) { - Promise.all(promises).then(( ) => { - callback(keyvalStore); - }); - return; - } - const { key, value } = entry; - keyvalStore[key] = value; - if ( entry.value instanceof Blob === false ) { return; } - promises.push(decompress(keyvalStore, key, value)); - }).catch(reason => { - console.info(`cacheStorage.getAllFromDb() failed: ${reason}`); - callback(); - }); - }; - - // https://github.com/uBlockOrigin/uBlock-issues/issues/141 - // Mind that IDBDatabase.transaction() and IDBObjectStore.put() - // can throw: - // https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/transaction - // https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/put - - const putToDb = async function(keyvalStore, callback) { - if ( typeof callback !== 'function' ) { - callback = noopfn; - } - const keys = Object.keys(keyvalStore); - if ( keys.length === 0 ) { return callback(); } - const promises = [ getDb() ]; - const entries = []; - const dontCompress = - µBlock.hiddenSettings.cacheStorageCompression !== true; for ( const key of keys ) { - const value = keyvalStore[key]; - const isString = typeof value === 'string'; - if ( isString === false || dontCompress ) { - entries.push({ key, value }); - continue; - } - promises.push(compress(entries, key, value)); - } - const finish = ( ) => { - if ( callback === undefined ) { return; } - let cb = callback; - callback = undefined; - cb(); - }; - try { - const results = await Promise.all(promises); - const db = results[0]; - if ( !db ) { return callback(); } - const transaction = db.transaction( - STORAGE_NAME, - 'readwrite' - ); - transaction.oncomplete = - transaction.onerror = - transaction.onabort = finish; - const table = transaction.objectStore(STORAGE_NAME); - for ( const entry of entries ) { - table.put(entry); - } - } catch (ex) { - finish(); - } - }; - - const deleteFromDb = async function(input, callback) { - if ( typeof callback !== 'function' ) { - callback = noopfn; - } - const keys = Array.isArray(input) ? input.slice() : [ input ]; - if ( keys.length === 0 ) { return callback(); } - const finish = ( ) => { - if ( callback === undefined ) { return; } - let cb = callback; - callback = undefined; - cb(); - }; - try { - const db = await getDb(); - if ( !db ) { return callback(); } - const transaction = db.transaction(STORAGE_NAME, 'readwrite'); - transaction.oncomplete = - transaction.onerror = - transaction.onabort = finish; - const table = transaction.objectStore(STORAGE_NAME); - for ( const key of keys ) { - table.delete(key); - } - } catch (ex) { - finish(); - } - }; - - const clearDb = async function(callback) { - if ( typeof callback !== 'function' ) { - callback = noopfn; - } - try { - const db = await getDb(); - if ( !db ) { return callback(); } - const transaction = db.transaction(STORAGE_NAME, 'readwrite'); - transaction.oncomplete = - transaction.onerror = - transaction.onabort = ( ) => { - callback(); - }; - transaction.objectStore(STORAGE_NAME).clear(); - } - catch(reason) { - console.info(`cacheStorage.clearDb() failed: ${reason}`); - callback(); - } - }; - - await getDb(); - if ( !db ) { return false; } - - api.name = 'indexedDB'; - api.get = function get(keys) { - return new Promise(resolve => { - if ( keys === null ) { - return getAllFromDb(bin => resolve(bin)); - } - let toRead, output = {}; - if ( typeof keys === 'string' ) { - toRead = [ keys ]; - } else if ( Array.isArray(keys) ) { - toRead = keys; - } else /* if ( typeof keys === 'object' ) */ { - toRead = Object.keys(keys); - output = keys; - } - getFromDb(toRead, output, bin => resolve(bin)); - }); - }; - api.set = function set(keys) { - return new Promise(resolve => { - putToDb(keys, details => resolve(details)); - }); - }; - api.remove = function remove(keys) { - return new Promise(resolve => { - deleteFromDb(keys, ( ) => resolve()); - }); - }; - api.clear = function clear() { - return new Promise(resolve => { - clearDb(( ) => resolve()); - }); - }; - api.getBytesInUse = function getBytesInUse() { - return Promise.resolve(0); - }; - return true; - }; - - // https://github.com/uBlockOrigin/uBlock-issues/issues/328 - // Delete cache-related entries from webext storage. - const clearWebext = async function() { - const bin = await webext.storage.local.get('assetCacheRegistry'); - if ( - bin instanceof Object === false || - bin.assetCacheRegistry instanceof Object === false - ) { - return; - } - const toRemove = [ - 'assetCacheRegistry', - 'assetSourceRegistry', - 'resourcesSelfie', - 'selfie' - ]; - for ( const key in bin.assetCacheRegistry ) { - if ( bin.assetCacheRegistry.hasOwnProperty(key) ) { - toRemove.push('cache/' + key); + const req = table.get(key); + req.onsuccess = gotOne; + req.onerror = noopfn; } } - webext.storage.local.remove(toRemove); + catch(reason) { + console.info(`cacheStorage.getFromDb() failed: ${reason}`); + callback(); + } }; - const clearIDB = function() { + const visitAllFromDb = async function(visitFn) { + const db = await getDb(); + if ( !db ) { return visitFn(); } + const transaction = db.transaction(STORAGE_NAME, 'readonly'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = ( ) => visitFn(); + const table = transaction.objectStore(STORAGE_NAME); + const req = table.openCursor(); + req.onsuccess = function(ev) { + let cursor = ev.target && ev.target.result; + if ( !cursor ) { return; } + let entry = cursor.value; + visitFn(entry); + cursor.continue(); + }; + }; + + const getAllFromDb = function(callback) { + if ( typeof callback !== 'function' ) { return; } + const promises = []; + const keyvalStore = {}; + visitAllFromDb(entry => { + if ( entry === undefined ) { + Promise.all(promises).then(( ) => { + callback(keyvalStore); + }); + return; + } + const { key, value } = entry; + keyvalStore[key] = value; + if ( entry.value instanceof Blob === false ) { return; } + promises.push(decompress(keyvalStore, key, value)); + }).catch(reason => { + console.info(`cacheStorage.getAllFromDb() failed: ${reason}`); + callback(); + }); + }; + + // https://github.com/uBlockOrigin/uBlock-issues/issues/141 + // Mind that IDBDatabase.transaction() and IDBObjectStore.put() + // can throw: + // https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/transaction + // https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/put + + const putToDb = async function(keyvalStore, callback) { + if ( typeof callback !== 'function' ) { + callback = noopfn; + } + const keys = Object.keys(keyvalStore); + if ( keys.length === 0 ) { return callback(); } + const promises = [ getDb() ]; + const entries = []; + const dontCompress = + µb.hiddenSettings.cacheStorageCompression !== true; + for ( const key of keys ) { + const value = keyvalStore[key]; + const isString = typeof value === 'string'; + if ( isString === false || dontCompress ) { + entries.push({ key, value }); + continue; + } + promises.push(compress(entries, key, value)); + } + const finish = ( ) => { + if ( callback === undefined ) { return; } + let cb = callback; + callback = undefined; + cb(); + }; try { - indexedDB.deleteDatabase(STORAGE_NAME); - } catch(ex) { + const results = await Promise.all(promises); + const db = results[0]; + if ( !db ) { return callback(); } + const transaction = db.transaction( + STORAGE_NAME, + 'readwrite' + ); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = finish; + const table = transaction.objectStore(STORAGE_NAME); + for ( const entry of entries ) { + table.put(entry); + } + } catch (ex) { + finish(); } }; - return api; -}()); + const deleteFromDb = async function(input, callback) { + if ( typeof callback !== 'function' ) { + callback = noopfn; + } + const keys = Array.isArray(input) ? input.slice() : [ input ]; + if ( keys.length === 0 ) { return callback(); } + const finish = ( ) => { + if ( callback === undefined ) { return; } + let cb = callback; + callback = undefined; + cb(); + }; + try { + const db = await getDb(); + if ( !db ) { return callback(); } + const transaction = db.transaction(STORAGE_NAME, 'readwrite'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = finish; + const table = transaction.objectStore(STORAGE_NAME); + for ( const key of keys ) { + table.delete(key); + } + } catch (ex) { + finish(); + } + }; + + const clearDb = async function(callback) { + if ( typeof callback !== 'function' ) { + callback = noopfn; + } + try { + const db = await getDb(); + if ( !db ) { return callback(); } + const transaction = db.transaction(STORAGE_NAME, 'readwrite'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = ( ) => { + callback(); + }; + transaction.objectStore(STORAGE_NAME).clear(); + } + catch(reason) { + console.info(`cacheStorage.clearDb() failed: ${reason}`); + callback(); + } + }; + + await getDb(); + if ( !db ) { return false; } + + cacheStorage.name = 'indexedDB'; + cacheStorage.get = function get(keys) { + return new Promise(resolve => { + if ( keys === null ) { + return getAllFromDb(bin => resolve(bin)); + } + let toRead, output = {}; + if ( typeof keys === 'string' ) { + toRead = [ keys ]; + } else if ( Array.isArray(keys) ) { + toRead = keys; + } else /* if ( typeof keys === 'object' ) */ { + toRead = Object.keys(keys); + output = keys; + } + getFromDb(toRead, output, bin => resolve(bin)); + }); + }; + cacheStorage.set = function set(keys) { + return new Promise(resolve => { + putToDb(keys, details => resolve(details)); + }); + }; + cacheStorage.remove = function remove(keys) { + return new Promise(resolve => { + deleteFromDb(keys, ( ) => resolve()); + }); + }; + cacheStorage.clear = function clear() { + return new Promise(resolve => { + clearDb(( ) => resolve()); + }); + }; + cacheStorage.getBytesInUse = function getBytesInUse() { + return Promise.resolve(0); + }; + return true; +}; + +// https://github.com/uBlockOrigin/uBlock-issues/issues/328 +// Delete cache-related entries from webext storage. +const clearWebext = async function() { + const bin = await webext.storage.local.get('assetCacheRegistry'); + if ( + bin instanceof Object === false || + bin.assetCacheRegistry instanceof Object === false + ) { + return; + } + const toRemove = [ + 'assetCacheRegistry', + 'assetSourceRegistry', + 'resourcesSelfie', + 'selfie' + ]; + for ( const key in bin.assetCacheRegistry ) { + if ( bin.assetCacheRegistry.hasOwnProperty(key) ) { + toRemove.push('cache/' + key); + } + } + webext.storage.local.remove(toRemove); +}; + +const clearIDB = function() { + try { + indexedDB.deleteDatabase(STORAGE_NAME); + } catch(ex) { + } +}; + +/******************************************************************************/ + +export default cacheStorage; /******************************************************************************/ diff --git a/src/js/commands.js b/src/js/commands.js index 463e63c08..19583799a 100644 --- a/src/js/commands.js +++ b/src/js/commands.js @@ -24,24 +24,23 @@ /******************************************************************************/ import { hostnameFromURI } from './uri-utils.js'; -import µBlock from './background.js'; +import µb from './background.js'; /******************************************************************************/ -µBlock.canUseShortcuts = vAPI.commands instanceof Object; +µb.canUseShortcuts = vAPI.commands instanceof Object; // https://github.com/uBlockOrigin/uBlock-issues/issues/386 // Firefox 74 and above has complete shotcut assignment user interface. -µBlock.canUpdateShortcuts = - µBlock.canUseShortcuts && +µb.canUpdateShortcuts = + µb.canUseShortcuts && vAPI.webextFlavor.soup.has('firefox') && typeof vAPI.commands.update === 'function'; -if ( µBlock.canUpdateShortcuts ) { +if ( µb.canUpdateShortcuts ) { self.addEventListener( 'webextFlavor', ( ) => { - const µb = µBlock; µb.canUpdateShortcuts = vAPI.webextFlavor.major < 74; if ( µb.canUpdateShortcuts === false ) { return; } vAPI.storage.get('commandShortcuts').then(bin => { @@ -65,7 +64,7 @@ if ( µBlock.canUpdateShortcuts ) { // ***************************************************************************** // start of local namespace -if ( µBlock.canUseShortcuts === false ) { return; } +if ( µb.canUseShortcuts === false ) { return; } const relaxBlockingMode = (( ) => { const reloadTimers = new Map(); @@ -73,7 +72,6 @@ const relaxBlockingMode = (( ) => { return function(tab) { if ( tab instanceof Object === false || tab.id <= 0 ) { return; } - const µb = µBlock; const normalURL = µb.normalizeTabURL(tab.id, tab.url); if ( µb.getNetFilteringSwitch(normalURL) === false ) { return; } @@ -161,8 +159,6 @@ const relaxBlockingMode = (( ) => { })(); vAPI.commands.onCommand.addListener(async command => { - const µb = µBlock; - switch ( command ) { case 'launch-element-picker': case 'launch-element-zapper': { diff --git a/src/js/console.js b/src/js/console.js index f29d9f087..15e0129b7 100644 --- a/src/js/console.js +++ b/src/js/console.js @@ -21,15 +21,39 @@ 'use strict'; -self.log = (function() { - const noopFunc = function() {}; - const info = function(s) { console.log(`[uBO] ${s}`); }; - return { - get verbosity( ) { return; }, - set verbosity(level) { - this.info = console.info = level === 'info' ? info : noopFunc; - }, - info: noopFunc, - print: info, +/******************************************************************************/ + +function ubologSet(state = false) { + if ( state ) { + if ( ubolog.process instanceof Function ) { + ubolog.process(); + } + ubolog = ubologDo; + } else { + ubolog = ubologIgnore; + } +} + +function ubologDo(...args) { + console.info('[uBO]', ...args); +} + +function ubologIgnore() { +} + +let ubolog = (( ) => { + const pending = []; + const store = function(...args) { + pending.push(args); }; + store.process = function() { + for ( const args of pending ) { + ubologDo(...args); + } + }; + return store; })(); + +/******************************************************************************/ + +export { ubolog, ubologSet }; diff --git a/src/js/contextmenu.js b/src/js/contextmenu.js index e664c3364..1c8acf520 100644 --- a/src/js/contextmenu.js +++ b/src/js/contextmenu.js @@ -23,11 +23,11 @@ /******************************************************************************/ -import µBlock from './background.js'; +import µb from './background.js'; /******************************************************************************/ -µBlock.contextMenu = (( ) => { +const contextMenu = (( ) => { /******************************************************************************/ @@ -61,8 +61,8 @@ const onBlockElement = function(details, tab) { } } - µBlock.epickerArgs.mouse = true; - µBlock.elementPickerExec(tab.id, 0, `${tagName}\t${src}`); + µb.epickerArgs.mouse = true; + µb.elementPickerExec(tab.id, 0, `${tagName}\t${src}`); }; /******************************************************************************/ @@ -70,8 +70,8 @@ const onBlockElement = function(details, tab) { const onBlockElementInFrame = function(details, tab) { if ( tab === undefined ) { return; } if ( /^https?:\/\//.test(details.frameUrl) === false ) { return; } - µBlock.epickerArgs.mouse = false; - µBlock.elementPickerExec(tab.id, details.frameId); + µb.epickerArgs.mouse = false; + µb.elementPickerExec(tab.id, details.frameId); }; /******************************************************************************/ @@ -87,7 +87,7 @@ const onSubscribeToList = function(details) { const url = parsedURL.searchParams.get('location'); if ( url === null ) { return; } const title = parsedURL.searchParams.get('title') || '?'; - const hash = µBlock.selectedFilterLists.indexOf(parsedURL) !== -1 + const hash = µb.selectedFilterLists.indexOf(parsedURL) !== -1 ? '#subscribed' : ''; vAPI.tabs.open({ @@ -104,7 +104,7 @@ const onSubscribeToList = function(details) { const onTemporarilyAllowLargeMediaElements = function(details, tab) { if ( tab === undefined ) { return; } - let pageStore = µBlock.pageStoreFromTabId(tab.id); + const pageStore = µb.pageStoreFromTabId(tab.id); if ( pageStore === null ) { return; } pageStore.temporarilyAllowLargeMediaElements(true); }; @@ -166,8 +166,8 @@ let currentBits = 0; const update = function(tabId = undefined) { let newBits = 0; - if ( µBlock.userSettings.contextMenuEnabled && tabId !== undefined ) { - const pageStore = µBlock.pageStoreFromTabId(tabId); + if ( µb.userSettings.contextMenuEnabled && tabId !== undefined ) { + const pageStore = µb.pageStoreFromTabId(tabId); if ( pageStore && pageStore.getNetFilteringSwitch() ) { if ( pageStore.shouldApplySpecificCosmeticFilters(0) ) { newBits |= 0b0001; @@ -206,7 +206,7 @@ const update = function(tabId = undefined) { // looked up after closing a window. vAPI.contextMenu.onMustUpdate = async function(tabId = undefined) { - if ( µBlock.userSettings.contextMenuEnabled === false ) { + if ( µb.userSettings.contextMenuEnabled === false ) { return update(); } if ( tabId !== undefined ) { @@ -222,3 +222,9 @@ return { update: vAPI.contextMenu.onMustUpdate }; /******************************************************************************/ })(); + +/******************************************************************************/ + +export default contextMenu; + +/******************************************************************************/ diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index 3601a7229..02b01bb64 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -23,17 +23,22 @@ /******************************************************************************/ +import logger from './logger.js'; +import µb from './background.js'; + +import { + StaticExtFilteringHostnameDB, + StaticExtFilteringSessionDB, +} from './static-ext-filtering-db.js'; + import { domainFromHostname, entityFromDomain, hostnameFromURI, } from './uri-utils.js'; -import µBlock from './background.js'; - /******************************************************************************/ -const µb = µBlock; const cosmeticSurveyingMissCountMax = parseInt(vAPI.localStorage.getItem('cosmeticSurveyingMissCountMax'), 10) || 15; @@ -205,10 +210,10 @@ const FilterContainer = function() { this.selectorCacheTimer = null; // specific filters - this.specificFilters = new µb.staticExtFilteringEngine.HostnameBasedDB(2); + this.specificFilters = new StaticExtFilteringHostnameDB(2); // temporary filters - this.sessionFilterDB = new µb.staticExtFilteringEngine.SessionDB(); + this.sessionFilterDB = new StaticExtFilteringSessionDB(); // low generic cosmetic filters, organized by id/class then simple/complex. this.lowlyGeneric = Object.create(null); @@ -390,7 +395,7 @@ FilterContainer.prototype.compileGenericHideSelector = function( const { raw, compiled, pseudoclass } = parser.result; if ( compiled === undefined ) { const who = writer.properties.get('name') || '?'; - µb.logger.writeOne({ + logger.writeOne({ realm: 'message', type: 'error', text: `Invalid generic cosmetic filter in ${who}: ${raw}` @@ -437,7 +442,7 @@ FilterContainer.prototype.compileGenericHideSelector = function( return this.compileSpecificSelector(parser, '', false, writer); } const who = writer.properties.get('name') || '?'; - µb.logger.writeOne({ + logger.writeOne({ realm: 'message', type: 'error', text: `Invalid generic cosmetic filter in ${who}: ##${raw}` @@ -493,7 +498,7 @@ FilterContainer.prototype.compileGenericUnhideSelector = function( const { raw, compiled } = parser.result; if ( compiled === undefined ) { const who = writer.properties.get('name') || '?'; - µb.logger.writeOne({ + logger.writeOne({ realm: 'message', type: 'error', text: `Invalid cosmetic filter in ${who}: #@#${raw}` @@ -523,7 +528,7 @@ FilterContainer.prototype.compileSpecificSelector = function( const { raw, compiled, exception } = parser.result; if ( compiled === undefined ) { const who = writer.properties.get('name') || '?'; - µb.logger.writeOne({ + logger.writeOne({ realm: 'message', type: 'error', text: `Invalid cosmetic filter in ${who}: ##${raw}` @@ -1169,8 +1174,8 @@ FilterContainer.prototype.benchmark = async function() { /******************************************************************************/ -// Export +const cosmeticFilteringEngine = new FilterContainer(); -µBlock.cosmeticFilteringEngine = new FilterContainer(); +export default cosmeticFilteringEngine; /******************************************************************************/ diff --git a/src/js/dynamic-net-filtering.js b/src/js/dynamic-net-filtering.js index 6f2833027..47e8d3935 100644 --- a/src/js/dynamic-net-filtering.js +++ b/src/js/dynamic-net-filtering.js @@ -28,9 +28,9 @@ import '../lib/punycode.js'; import globals from './globals.js'; +import µb from './background.js'; import { domainFromHostname } from './uri-utils.js'; import { LineIterator } from './text-iterators.js'; -import µBlock from './background.js'; /******************************************************************************/ @@ -266,7 +266,7 @@ const Matrix = class { evaluateCellZ(srcHostname, desHostname, type) { - µBlock.decomposeHostname(srcHostname, this.decomposedSource); + µb.decomposeHostname(srcHostname, this.decomposedSource); this.type = type; const bitOffset = typeBitOffsets[type]; for ( const shn of this.decomposedSource ) { @@ -296,7 +296,7 @@ const Matrix = class { // Precedence: from most specific to least specific // Specific-destination, any party, any type - µBlock.decomposeHostname(desHostname, this.decomposedDestination); + µb.decomposeHostname(desHostname, this.decomposedDestination); for ( const dhn of this.decomposedDestination ) { if ( dhn === '*' ) { break; } this.y = dhn; @@ -509,13 +509,13 @@ const Matrix = class { async benchmark() { - const requests = await µBlock.loadBenchmarkDataset(); + const requests = await µb.loadBenchmarkDataset(); if ( Array.isArray(requests) === false || requests.length === 0 ) { - log.print('No requests found to benchmark'); + console.info('No requests found to benchmark'); return; } - log.print(`Benchmarking sessionFirewall.evaluateCellZY()...`); - const fctxt = µBlock.filteringContext.duplicate(); + console.info(`Benchmarking sessionFirewall.evaluateCellZY()...`); + const fctxt = µb.filteringContext.duplicate(); const t0 = self.performance.now(); for ( const request of requests ) { fctxt.setURL(request.url); @@ -529,8 +529,8 @@ const Matrix = class { } const t1 = self.performance.now(); const dur = t1 - t0; - log.print(`Evaluated ${requests.length} requests in ${dur.toFixed(0)} ms`); - log.print(`\tAverage: ${(dur / requests.length).toFixed(3)} ms per request`); + console.info(`Evaluated ${requests.length} requests in ${dur.toFixed(0)} ms`); + console.info(`\tAverage: ${(dur / requests.length).toFixed(3)} ms per request`); } }; @@ -544,11 +544,9 @@ Matrix.prototype.magicId = 1; /******************************************************************************/ -// Export +const sessionFirewall = new Matrix(); +const permanentFirewall = new Matrix(); -µBlock.Firewall = Matrix; - -µBlock.sessionFirewall = new µBlock.Firewall(); -µBlock.permanentFirewall = new µBlock.Firewall(); +export { permanentFirewall, sessionFirewall }; /******************************************************************************/ diff --git a/src/js/hnswitches.js b/src/js/hnswitches.js index 9df8a7494..fdc08f243 100644 --- a/src/js/hnswitches.js +++ b/src/js/hnswitches.js @@ -28,8 +28,8 @@ import '../lib/punycode.js'; import globals from './globals.js'; +import µb from './background.js'; import { LineIterator } from './text-iterators.js'; -import µBlock from './background.js'; /******************************************************************************/ @@ -228,7 +228,7 @@ HnSwitches.prototype.evaluateZ = function(switchName, hostname) { return false; } this.n = switchName; - µBlock.decomposeHostname(hostname, this.decomposedSource); + µb.decomposeHostname(hostname, this.decomposedSource); for ( const shn of this.decomposedSource ) { let bits = this.switches.get(shn); if ( bits !== undefined ) { @@ -323,11 +323,9 @@ HnSwitches.prototype.removeFromRuleParts = function(parts) { /******************************************************************************/ -// Export +const sessionSwitches = new HnSwitches(); +const permanentSwitches = new HnSwitches(); -µBlock.HnSwitches = HnSwitches; - -µBlock.sessionSwitches = new HnSwitches(); -µBlock.permanentSwitches = new HnSwitches(); +export { permanentSwitches, sessionSwitches }; /******************************************************************************/ diff --git a/src/js/hntrie.js b/src/js/hntrie.js index f18c13a90..abebf2d65 100644 --- a/src/js/hntrie.js +++ b/src/js/hntrie.js @@ -796,7 +796,7 @@ const getWasmModule = (( ) => { ).then( WebAssembly.compileStreaming ).catch(reason => { - log.info(reason); + console.info(reason); }); return wasmModulePromise; diff --git a/src/js/html-filtering.js b/src/js/html-filtering.js index 52e9bb962..a563826fb 100644 --- a/src/js/html-filtering.js +++ b/src/js/html-filtering.js @@ -23,22 +23,28 @@ /******************************************************************************/ -import µBlock from './background.js'; +import logger from './logger.js'; +import µb from './background.js'; +import { sessionFirewall } from './dynamic-net-filtering.js'; + +import { + StaticExtFilteringHostnameDB, + StaticExtFilteringSessionDB, +} from './static-ext-filtering-db.js'; /******************************************************************************/ -const µb = µBlock; const pselectors = new Map(); const duplicates = new Set(); -const filterDB = new µb.staticExtFilteringEngine.HostnameBasedDB(2); -const sessionFilterDB = new µb.staticExtFilteringEngine.SessionDB(); +const filterDB = new StaticExtFilteringHostnameDB(2); +const sessionFilterDB = new StaticExtFilteringSessionDB(); let acceptedCount = 0; let discardedCount = 0; let docRegister; -const api = { +const htmlFilteringEngine = { get acceptedCount() { return acceptedCount; }, @@ -237,7 +243,7 @@ PSelector.prototype.operatorToTaskMap = new Map([ PSelector.prototype.invalid = false; const logOne = function(details, exception, selector) { - µBlock.filteringContext + µb.filteringContext .duplicate() .fromTabId(details.tabId) .setRealm('extended') @@ -263,7 +269,7 @@ const applyProceduralSelector = function(details, selector) { node.remove(); modified = true; } - if ( modified && µb.logger.enabled ) { + if ( modified && logger.enabled ) { logOne(details, 0, pselector.raw); } return modified; @@ -276,13 +282,13 @@ const applyCSSSelector = function(details, selector) { node.remove(); modified = true; } - if ( modified && µb.logger.enabled ) { + if ( modified && logger.enabled ) { logOne(details, 0, selector); } return modified; }; -api.reset = function() { +htmlFilteringEngine.reset = function() { filterDB.clear(); pselectors.clear(); duplicates.clear(); @@ -290,16 +296,16 @@ api.reset = function() { discardedCount = 0; }; -api.freeze = function() { +htmlFilteringEngine.freeze = function() { duplicates.clear(); filterDB.collectGarbage(); }; -api.compile = function(parser, writer) { +htmlFilteringEngine.compile = function(parser, writer) { const { raw, compiled, exception } = parser.result; if ( compiled === undefined ) { const who = writer.properties.get('name') || '?'; - µb.logger.writeOne({ + logger.writeOne({ realm: 'message', type: 'error', text: `Invalid HTML filter in ${who}: ##${raw}` @@ -325,14 +331,14 @@ api.compile = function(parser, writer) { } }; -api.compileTemporary = function(parser) { +htmlFilteringEngine.compileTemporary = function(parser) { return { session: sessionFilterDB, selector: parser.result.compiled, }; }; -api.fromCompiledContent = function(reader) { +htmlFilteringEngine.fromCompiledContent = function(reader) { // Don't bother loading filters if stream filtering is not supported. if ( µb.canFilterResponseData === false ) { return; } @@ -351,11 +357,11 @@ api.fromCompiledContent = function(reader) { } }; -api.getSession = function() { +htmlFilteringEngine.getSession = function() { return sessionFilterDB; }; -api.retrieve = function(details) { +htmlFilteringEngine.retrieve = function(details) { const hostname = details.hostname; const plains = new Set(); @@ -384,7 +390,7 @@ api.retrieve = function(details) { // Do not filter if the site is under an `allow` rule. if ( µb.userSettings.advancedUserEnabled && - µb.sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 + sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 ) { return; } @@ -413,7 +419,7 @@ api.retrieve = function(details) { } }; -api.apply = function(doc, details) { +htmlFilteringEngine.apply = function(doc, details) { docRegister = doc; let modified = false; for ( const selector of details.selectors.plains ) { @@ -430,19 +436,17 @@ api.apply = function(doc, details) { return modified; }; -api.toSelfie = function() { +htmlFilteringEngine.toSelfie = function() { return filterDB.toSelfie(); }; -api.fromSelfie = function(selfie) { +htmlFilteringEngine.fromSelfie = function(selfie) { filterDB.fromSelfie(selfie); pselectors.clear(); }; /******************************************************************************/ -// Export - -µBlock.htmlFilteringEngine = api; +export default htmlFilteringEngine; /******************************************************************************/ diff --git a/src/js/httpheader-filtering.js b/src/js/httpheader-filtering.js index 19c6b40e7..3212bb660 100644 --- a/src/js/httpheader-filtering.js +++ b/src/js/httpheader-filtering.js @@ -23,15 +23,21 @@ /******************************************************************************/ +import logger from './logger.js'; +import µb from './background.js'; import { entityFromDomain } from './uri-utils.js'; -import µBlock from './background.js'; +import { sessionFirewall } from './dynamic-net-filtering.js'; + +import { + StaticExtFilteringHostnameDB, + StaticExtFilteringSessionDB, +} from './static-ext-filtering-db.js'; /******************************************************************************/ -const µb = µBlock; const duplicates = new Set(); -const filterDB = new µb.staticExtFilteringEngine.HostnameBasedDB(1); -const sessionFilterDB = new µb.staticExtFilteringEngine.SessionDB(); +const filterDB = new StaticExtFilteringHostnameDB(1); +const sessionFilterDB = new StaticExtFilteringSessionDB(); const $headers = new Set(); const $exceptions = new Set(); @@ -62,7 +68,7 @@ const logOne = function(isException, token, fctxt) { .toLogger(); }; -const api = { +const httpheaderFilteringEngine = { get acceptedCount() { return acceptedCount; }, @@ -71,19 +77,19 @@ const api = { } }; -api.reset = function() { +httpheaderFilteringEngine.reset = function() { filterDB.clear(); duplicates.clear(); acceptedCount = 0; discardedCount = 0; }; -api.freeze = function() { +httpheaderFilteringEngine.freeze = function() { duplicates.clear(); filterDB.collectGarbage(); }; -api.compile = function(parser, writer) { +httpheaderFilteringEngine.compile = function(parser, writer) { writer.select(µb.compiledHTTPHeaderSection); const { compiled, exception } = parser.result; @@ -117,7 +123,7 @@ api.compile = function(parser, writer) { } }; -api.compileTemporary = function(parser) { +httpheaderFilteringEngine.compileTemporary = function(parser) { return { session: sessionFilterDB, selector: parser.result.compiled.slice(15, -1), @@ -129,7 +135,7 @@ api.compileTemporary = function(parser) { // ^ ^ // 15 -1 -api.fromCompiledContent = function(reader) { +httpheaderFilteringEngine.fromCompiledContent = function(reader) { reader.select(µb.compiledHTTPHeaderSection); while ( reader.next() ) { @@ -146,11 +152,11 @@ api.fromCompiledContent = function(reader) { } }; -api.getSession = function() { +httpheaderFilteringEngine.getSession = function() { return sessionFilterDB; }; -api.apply = function(fctxt, headers) { +httpheaderFilteringEngine.apply = function(fctxt, headers) { if ( filterDB.size === 0 ) { return; } const hostname = fctxt.getHostname(); @@ -178,13 +184,12 @@ api.apply = function(fctxt, headers) { // Do not filter response headers if the site is under an `allow` rule. if ( µb.userSettings.advancedUserEnabled && - µb.sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 + sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 ) { return; } const hasGlobalException = $exceptions.has(''); - const loggerEnabled = µb.logger.enabled; let modified = false; @@ -194,13 +199,13 @@ api.apply = function(fctxt, headers) { if ( i === -1 ) { break; } const isExcepted = hasGlobalException || $exceptions.has(name); if ( isExcepted ) { - if ( loggerEnabled ) { + if ( logger.enabled ) { logOne(true, hasGlobalException ? '' : name, fctxt); } break; } headers.splice(i, 1); - if ( loggerEnabled ) { + if ( logger.enabled ) { logOne(false, name, fctxt); } modified = true; @@ -210,18 +215,16 @@ api.apply = function(fctxt, headers) { return modified; }; -api.toSelfie = function() { +httpheaderFilteringEngine.toSelfie = function() { return filterDB.toSelfie(); }; -api.fromSelfie = function(selfie) { +httpheaderFilteringEngine.fromSelfie = function(selfie) { filterDB.fromSelfie(selfie); }; /******************************************************************************/ -// Export - -µb.httpheaderFilteringEngine = api; +export default httpheaderFilteringEngine; /******************************************************************************/ diff --git a/src/js/logger.js b/src/js/logger.js index eb417d306..53cdc3d1f 100644 --- a/src/js/logger.js +++ b/src/js/logger.js @@ -23,10 +23,6 @@ /******************************************************************************/ -import µBlock from './background.js'; - -/******************************************************************************/ - let buffer = null; let lastReadTime = 0; let writePtr = 0; @@ -40,10 +36,10 @@ const janitor = ( ) => { buffer !== null && lastReadTime < (Date.now() - logBufferObsoleteAfter) ) { - api.enabled = false; + logger.enabled = false; buffer = null; writePtr = 0; - api.ownerId = undefined; + logger.ownerId = undefined; vAPI.messaging.broadcast({ what: 'loggerDisabled' }); } if ( buffer !== null ) { @@ -58,7 +54,7 @@ const boxEntry = function(details) { return JSON.stringify(details); }; -const api = { +const logger = { enabled: false, ownerId: undefined, writeOne: function(details) { @@ -87,8 +83,6 @@ const api = { /******************************************************************************/ -// Export - -µBlock.logger = api; +export default logger; /******************************************************************************/ diff --git a/src/js/lz4.js b/src/js/lz4.js index 539fc8a94..0c91b2f76 100644 --- a/src/js/lz4.js +++ b/src/js/lz4.js @@ -25,7 +25,7 @@ /******************************************************************************/ -import µBlock from './background.js'; +import µb from './background.js'; /******************************************************************************* @@ -46,7 +46,7 @@ let ttlTimer; let ttlDelay = 60000; const init = function() { - ttlDelay = µBlock.hiddenSettings.autoUpdateAssetFetchPeriod * 1000 + 15000; + ttlDelay = µb.hiddenSettings.autoUpdateAssetFetchPeriod * 1000 + 15000; if ( lz4CodecInstance === null ) { return Promise.resolve(null); } @@ -55,7 +55,7 @@ const init = function() { } if ( pendingInitialization === undefined ) { let flavor; - if ( µBlock.hiddenSettings.disableWebAssembly === true ) { + if ( µb.hiddenSettings.disableWebAssembly === true ) { flavor = 'js'; } pendingInitialization = lz4BlockCodec.createInstance(flavor) @@ -155,7 +155,7 @@ const decodeValue = function(inputArray) { return s; }; -µBlock.lz4Codec = { +const lz4Codec = { // Arguments: // dataIn: must be a string // Returns: @@ -199,3 +199,7 @@ const decodeValue = function(inputArray) { }; /******************************************************************************/ + +export default lz4Codec; + +/******************************************************************************/ diff --git a/src/js/messaging.js b/src/js/messaging.js index ab161940f..c14d746db 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -26,7 +26,30 @@ import '../lib/publicsuffixlist/publicsuffixlist.js'; import '../lib/punycode.js'; +import cacheStorage from './cachestorage.js'; +import cosmeticFilteringEngine from './cosmetic-filtering.js'; import globals from './globals.js'; +import logger from './logger.js'; +import lz4Codec from './lz4.js'; +import io from './assets.js'; +import scriptletFilteringEngine from './scriptlet-filtering.js'; +import staticExtFilteringEngine from './static-ext-filtering.js'; +import staticFilteringReverseLookup from './reverselookup.js'; +import staticNetFilteringEngine from './static-net-filtering.js'; +import µb from './background.js'; +import { redirectEngine } from './redirect-engine.js'; +import { StaticFilteringParser } from './static-filtering-parser.js'; +import { webRequest } from './traffic.js'; + +import { + permanentFirewall, + sessionFirewall, +} from './dynamic-net-filtering.js'; + +import { + permanentSwitches, + sessionSwitches, +} from './hnswitches.js'; import { domainFromHostname, @@ -36,8 +59,10 @@ import { isNetworkURI, } from './uri-utils.js'; -import { StaticFilteringParser } from './static-filtering-parser.js'; -import µBlock from './background.js'; +import { + permanentURLFiltering, + sessionURLFiltering, +} from './url-net-filtering.js'; /******************************************************************************/ @@ -56,8 +81,6 @@ import µBlock from './background.js'; { // >>>>> start of local scope -const µb = µBlock; - const clickToLoad = function(request, sender) { const { tabId, frameId } = sender; if ( tabId === undefined || frameId === undefined ) { return false; } @@ -81,7 +104,7 @@ const onMessage = function(request, sender, callback) { switch ( request.what ) { case 'getAssetContent': // https://github.com/chrisaljoudi/uBlock/issues/417 - µb.assets.get(request.url, { + io.get(request.url, { dontCache: true, needSourceURL: true, }).then(result => { @@ -90,7 +113,7 @@ const onMessage = function(request, sender, callback) { return; case 'listsFromNetFilter': - µb.staticFilteringReverseLookup.fromNetFilter( + staticFilteringReverseLookup.fromNetFilter( request.rawFilter ).then(response => { callback(response); @@ -98,7 +121,7 @@ const onMessage = function(request, sender, callback) { return; case 'listsFromCosmeticFilter': - µb.staticFilteringReverseLookup.fromCosmeticFilter( + staticFilteringReverseLookup.fromCosmeticFilter( request ).then(response => { callback(response); @@ -115,9 +138,9 @@ const onMessage = function(request, sender, callback) { case 'sfneBenchmark': µb.loadBenchmarkDataset().then(requests => { - µb.staticNetFilteringEngine.benchmark( + staticNetFilteringEngine.benchmark( requests, - { redirectEngine: µb.redirectEngine } + { redirectEngine } ).then(result => { callback(result); }); @@ -146,7 +169,7 @@ const onMessage = function(request, sender, callback) { case 'forceUpdateAssets': µb.scheduleAssetUpdater(0); - µb.assets.updateStart({ + io.updateStart({ delay: µb.hiddenSettings.manualUpdateAssetFetchPeriod }); break; @@ -241,8 +264,6 @@ vAPI.messaging.setup(onMessage); { // >>>>> start of local scope -const µb = µBlock; - const createCounts = ( ) => { return { blocked: { any: 0, frame: 0, script: 0 }, @@ -291,7 +312,7 @@ const firewallRuleTypes = [ const getFirewallRules = function(src, out) { const ruleset = out.firewallRules = {}; - const df = µb.sessionFirewall; + const df = sessionFirewall; for ( const type of firewallRuleTypes ) { const r = df.lookupRuleData('*', '*', type); @@ -358,26 +379,26 @@ const popupDataFromTabId = function(tabId, tabTitle) { r.contentLastModified = pageStore.contentLastModified; getFirewallRules(rootHostname, r); r.canElementPicker = isNetworkURI(r.rawURL); - r.noPopups = µb.sessionSwitches.evaluateZ( + r.noPopups = sessionSwitches.evaluateZ( 'no-popups', rootHostname ); r.popupBlockedCount = pageStore.popupBlockedCount; - r.noCosmeticFiltering = µb.sessionSwitches.evaluateZ( + r.noCosmeticFiltering = sessionSwitches.evaluateZ( 'no-cosmetic-filtering', rootHostname ); - r.noLargeMedia = µb.sessionSwitches.evaluateZ( + r.noLargeMedia = sessionSwitches.evaluateZ( 'no-large-media', rootHostname ); r.largeMediaCount = pageStore.largeMediaCount; - r.noRemoteFonts = µb.sessionSwitches.evaluateZ( + r.noRemoteFonts = sessionSwitches.evaluateZ( 'no-remote-fonts', rootHostname ); r.remoteFontCount = pageStore.remoteFontCount; - r.noScripting = µb.sessionSwitches.evaluateZ( + r.noScripting = sessionSwitches.evaluateZ( 'no-scripting', rootHostname ); @@ -386,14 +407,14 @@ const popupDataFromTabId = function(tabId, tabTitle) { getFirewallRules(undefined, r); } - r.matrixIsDirty = µb.sessionFirewall.hasSameRules( - µb.permanentFirewall, + r.matrixIsDirty = sessionFirewall.hasSameRules( + permanentFirewall, rootHostname, r.hostnameDict ) === false; if ( r.matrixIsDirty === false ) { - r.matrixIsDirty = µb.sessionSwitches.hasSameRules( - µb.permanentSwitches, + r.matrixIsDirty = sessionSwitches.hasSameRules( + permanentSwitches, rootHostname ) === false; } @@ -470,17 +491,17 @@ const onMessage = function(request, sender, callback) { break; case 'revertFirewallRules': - µb.sessionFirewall.copyRules( - µb.permanentFirewall, + sessionFirewall.copyRules( + permanentFirewall, request.srcHostname, request.desHostnames ); - µb.sessionSwitches.copyRules( - µb.permanentSwitches, + sessionSwitches.copyRules( + permanentSwitches, request.srcHostname ); // https://github.com/gorhill/uBlock/issues/188 - µb.cosmeticFilteringEngine.removeFromSelectorCache( + cosmeticFilteringEngine.removeFromSelectorCache( request.srcHostname, 'net' ); @@ -490,8 +511,8 @@ const onMessage = function(request, sender, callback) { case 'saveFirewallRules': if ( - µb.permanentFirewall.copyRules( - µb.sessionFirewall, + permanentFirewall.copyRules( + sessionFirewall, request.srcHostname, request.desHostnames ) @@ -499,8 +520,8 @@ const onMessage = function(request, sender, callback) { µb.savePermanentFirewallRules(); } if ( - µb.permanentSwitches.copyRules( - µb.sessionSwitches, + permanentSwitches.copyRules( + sessionSwitches, request.srcHostname ) ) { @@ -556,8 +577,6 @@ vAPI.messaging.listen({ { // >>>>> start of local scope -const µb = µBlock; - const retrieveContentScriptParameters = async function(sender, request) { if ( µb.readyToFilter !== true ) { return; } const { tabId, frameId } = sender; @@ -575,7 +594,6 @@ const retrieveContentScriptParameters = async function(sender, request) { request.url = pageStore.getEffectiveFrameURL(sender); } - const loggerEnabled = µb.logger.enabled; const noSpecificCosmeticFiltering = pageStore.shouldApplySpecificCosmeticFilters(frameId) === false; const noGenericCosmeticFiltering = @@ -594,13 +612,13 @@ const retrieveContentScriptParameters = async function(sender, request) { request.entity = entityFromDomain(request.domain); response.specificCosmeticFilters = - µb.cosmeticFilteringEngine.retrieveSpecificSelectors(request, response); + cosmeticFilteringEngine.retrieveSpecificSelectors(request, response); // The procedural filterer's code is loaded only when needed and must be // present before returning response to caller. if ( Array.isArray(response.specificCosmeticFilters.proceduralFilters) || ( - loggerEnabled && + logger.enabled && response.specificCosmeticFilters.exceptedFilters.length !== 0 ) ) { @@ -620,14 +638,14 @@ const retrieveContentScriptParameters = async function(sender, request) { µb.canInjectScriptletsNow === false || isNetworkURI(sender.frameURL) === false ) { - response.scriptlets = µb.scriptletFilteringEngine.retrieve(request); + response.scriptlets = scriptletFilteringEngine.retrieve(request); } // https://github.com/NanoMeow/QuickReports/issues/6#issuecomment-414516623 // Inject as early as possible to make the cosmetic logger code less // sensitive to the removal of DOM nodes which may match injected // cosmetic filters. - if ( loggerEnabled ) { + if ( logger.enabled ) { if ( noSpecificCosmeticFiltering === false || noGenericCosmeticFiltering === false @@ -666,7 +684,7 @@ const onMessage = function(request, sender, callback) { switch ( request.what ) { case 'cosmeticFiltersInjected': - µb.cosmeticFilteringEngine.addToSelectorCache(request); + cosmeticFilteringEngine.addToSelectorCache(request); break; case 'getCollapsibleBlockedRequests': @@ -674,7 +692,7 @@ const onMessage = function(request, sender, callback) { id: request.id, hash: request.hash, netSelectorCacheCountMax: - µb.cosmeticFilteringEngine.netSelectorCacheCountMax, + cosmeticFilteringEngine.netSelectorCacheCountMax, }; if ( µb.userSettings.collapseBlocked && @@ -705,7 +723,7 @@ const onMessage = function(request, sender, callback) { request.tabId = sender.tabId; request.frameId = sender.frameId; response = { - result: µb.cosmeticFilteringEngine.retrieveGenericSelectors(request), + result: cosmeticFilteringEngine.retrieveGenericSelectors(request), }; break; @@ -735,8 +753,6 @@ vAPI.messaging.listen({ // >>>>> start of local scope const onMessage = function(request, sender, callback) { - const µb = µBlock; - // Async switch ( request.what ) { // The procedural filterer must be present in case the user wants to @@ -801,7 +817,7 @@ const fromBase64 = function(encoded) { } let u8array; try { - u8array = µBlock.denseBase64.decode(encoded); + u8array = µb.denseBase64.decode(encoded); } catch(ex) { } return Promise.resolve(u8array !== undefined ? u8array : encoded); @@ -809,22 +825,22 @@ const fromBase64 = function(encoded) { const toBase64 = function(data) { const value = data instanceof Uint8Array - ? µBlock.denseBase64.encode(data) + ? µb.denseBase64.encode(data) : data; return Promise.resolve(value); }; const compress = function(json) { - return µBlock.lz4Codec.encode(json, toBase64); + return lz4Codec.encode(json, toBase64); }; const decompress = function(encoded) { - return µBlock.lz4Codec.decode(encoded, fromBase64); + return lz4Codec.decode(encoded, fromBase64); }; const onMessage = function(request, sender, callback) { // Cloud storage support is optional. - if ( µBlock.cloudStorageSupported !== true ) { + if ( µb.cloudStorageSupported !== true ) { callback(); return; } @@ -833,7 +849,7 @@ const onMessage = function(request, sender, callback) { switch ( request.what ) { case 'cloudGetOptions': vAPI.cloud.getOptions(function(options) { - options.enabled = µBlock.userSettings.cloudStorageEnabled === true; + options.enabled = µb.userSettings.cloudStorageEnabled === true; callback(options); }); return; @@ -849,7 +865,7 @@ const onMessage = function(request, sender, callback) { }); case 'cloudPush': - if ( µBlock.hiddenSettings.cloudStorageCompression ) { + if ( µb.hiddenSettings.cloudStorageCompression ) { request.encode = compress; } return vAPI.cloud.push(request).then(result => { @@ -901,8 +917,6 @@ vAPI.messaging.listen({ { // >>>>> start of local scope -const µb = µBlock; - // Settings const getLocalData = async function() { const data = Object.assign({}, µb.restoreBackupSettings); @@ -924,9 +938,9 @@ const backupUserData = async function() { hiddenSettings: µb.getModifiedSettings(µb.hiddenSettings, µb.hiddenSettingsDefault), whitelist: µb.arrayFromWhitelist(µb.netWhitelist), - dynamicFilteringString: µb.permanentFirewall.toString(), - urlFilteringString: µb.permanentURLFiltering.toString(), - hostnameSwitchesString: µb.permanentSwitches.toString(), + dynamicFilteringString: permanentFirewall.toString(), + urlFilteringString: permanentURLFiltering.toString(), + hostnameSwitchesString: permanentSwitches.toString(), userFilters: userFilters.content, }; @@ -962,17 +976,17 @@ const restoreUserData = async function(request) { // https://github.com/chrisaljoudi/uBlock/issues/1102 // Ensure all currently cached assets are flushed from storage AND memory. - µb.assets.rmrf(); + io.rmrf(); // If we are going to restore all, might as well wipe out clean local // storages await Promise.all([ - µb.cacheStorage.clear(), + cacheStorage.clear(), vAPI.storage.clear(), ]); // Restore block stats - µBlock.saveLocalSettings(); + µb.saveLocalSettings(); // Restore user data vAPI.storage.set(userData.userSettings); @@ -980,7 +994,7 @@ const restoreUserData = async function(request) { // Restore advanced settings. let hiddenSettings = userData.hiddenSettings; if ( hiddenSettings instanceof Object === false ) { - hiddenSettings = µBlock.hiddenSettingsFromString( + hiddenSettings = µb.hiddenSettingsFromString( userData.hiddenSettingsString || '' ); } @@ -1027,7 +1041,7 @@ const restoreUserData = async function(request) { // quite attached to numbers const resetUserData = async function() { await Promise.all([ - µb.cacheStorage.clear(), + cacheStorage.clear(), vAPI.storage.clear(), ]); @@ -1056,17 +1070,17 @@ const getLists = async function(callback) { autoUpdate: µb.userSettings.autoUpdate, available: null, cache: null, - cosmeticFilterCount: µb.cosmeticFilteringEngine.getFilterCount(), + cosmeticFilterCount: cosmeticFilteringEngine.getFilterCount(), current: µb.availableFilterLists, ignoreGenericCosmeticFilters: µb.userSettings.ignoreGenericCosmeticFilters, - isUpdating: µb.assets.isUpdating(), - netFilterCount: µb.staticNetFilteringEngine.getFilterCount(), + isUpdating: io.isUpdating(), + netFilterCount: staticNetFilteringEngine.getFilterCount(), parseCosmeticFilters: µb.userSettings.parseAllABPHideFilters, userFiltersPath: µb.userFiltersPath }; const [ lists, metadata ] = await Promise.all([ µb.getAvailableLists(), - µb.assets.metadata(), + io.metadata(), ]); r.available = lists; prepListEntries(r.available); @@ -1099,14 +1113,14 @@ const getOriginHints = function() { const getRules = function() { return { permanentRules: - µb.permanentFirewall.toArray().concat( - µb.permanentSwitches.toArray(), - µb.permanentURLFiltering.toArray() + permanentFirewall.toArray().concat( + permanentSwitches.toArray(), + permanentURLFiltering.toArray() ), sessionRules: - µb.sessionFirewall.toArray().concat( - µb.sessionSwitches.toArray(), - µb.sessionURLFiltering.toArray() + sessionFirewall.toArray().concat( + sessionSwitches.toArray(), + sessionURLFiltering.toArray() ), pslSelfie: globals.publicSuffixList.toSelfie(), }; @@ -1115,13 +1129,13 @@ const getRules = function() { const modifyRuleset = function(details) { let swRuleset, hnRuleset, urlRuleset; if ( details.permanent ) { - swRuleset = µb.permanentSwitches; - hnRuleset = µb.permanentFirewall; - urlRuleset = µb.permanentURLFiltering; + swRuleset = permanentSwitches; + hnRuleset = permanentFirewall; + urlRuleset = permanentURLFiltering; } else { - swRuleset = µb.sessionSwitches; - hnRuleset = µb.sessionFirewall; - urlRuleset = µb.sessionURLFiltering; + swRuleset = sessionSwitches; + hnRuleset = sessionFirewall; + urlRuleset = sessionURLFiltering; } let toRemove = new Set(details.toRemove.trim().split(/\s*[\n\r]+\s*/)); for ( let rule of toRemove ) { @@ -1240,7 +1254,7 @@ const onMessage = function(request, sender, callback) { case 'getAutoCompleteDetails': response = {}; if ( (request.hintUpdateToken || 0) === 0 ) { - response.redirectResources = µb.redirectEngine.getResourceDetails(); + response.redirectResources = redirectEngine.getResourceDetails(); response.preparseDirectiveTokens = µb.preparseDirectives.getTokens(); response.preparseDirectiveHints = µb.preparseDirectives.getHints(); response.expertMode = µb.hiddenSettings.filterAuthorMode; @@ -1257,22 +1271,22 @@ const onMessage = function(request, sender, callback) { case 'modifyRuleset': // https://github.com/chrisaljoudi/uBlock/issues/772 - µb.cosmeticFilteringEngine.removeFromSelectorCache('*'); + cosmeticFilteringEngine.removeFromSelectorCache('*'); modifyRuleset(request); response = getRules(); break; case 'purgeAllCaches': if ( request.hard ) { - µb.assets.remove(/./); + io.remove(/./); } else { - µb.assets.purge(/./, 'public_suffix_list.dat'); + io.purge(/./, 'public_suffix_list.dat'); } break; case 'purgeCache': - µb.assets.purge(request.assetKey); - µb.assets.remove('compiled/' + request.assetKey); + io.purge(request.assetKey); + io.remove('compiled/' + request.assetKey); break; case 'readHiddenSettings': @@ -1325,7 +1339,6 @@ vAPI.messaging.listen({ { // >>>>> start of local scope -const µb = µBlock; const extensionOriginURL = vAPI.getURL(''); const documentBlockedURL = vAPI.getURL('document-blocked.html'); @@ -1333,7 +1346,7 @@ const getLoggerData = async function(details, activeTabId, callback) { const response = { activeTabId, colorBlind: µb.userSettings.colorBlindFriendly, - entries: µb.logger.readAll(details.ownerId), + entries: logger.readAll(details.ownerId), filterAuthorMode: µb.hiddenSettings.filterAuthorMode, tabIdsToken: µb.pageStoresToken, tooltips: µb.userSettings.tooltipsDisabled === false @@ -1386,8 +1399,8 @@ const getURLFilteringData = function(details) { dirty: false, colors: colors }; - const suf = µb.sessionURLFiltering; - const puf = µb.permanentURLFiltering; + const suf = sessionURLFiltering; + const puf = permanentURLFiltering; const urls = details.urls; const context = details.context; const type = details.type; @@ -1416,7 +1429,7 @@ const compileTemporaryException = function(filter) { const parser = new StaticFilteringParser(); parser.analyze(filter); if ( parser.shouldDiscard() ) { return; } - return µb.staticExtFilteringEngine.compileTemporary(parser); + return staticExtFilteringEngine.compileTemporary(parser); }; const toggleTemporaryException = function(details) { @@ -1443,8 +1456,8 @@ const onMessage = function(request, sender, callback) { switch ( request.what ) { case 'readAll': if ( - µb.logger.ownerId !== undefined && - µb.logger.ownerId !== request.ownerId + logger.ownerId !== undefined && + logger.ownerId !== request.ownerId ) { return callback({ unavailable: true }); } @@ -1466,14 +1479,14 @@ const onMessage = function(request, sender, callback) { break; case 'releaseView': - if ( request.ownerId === µb.logger.ownerId ) { - µb.logger.ownerId = undefined; + if ( request.ownerId === logger.ownerId ) { + logger.ownerId = undefined; } break; case 'saveURLFilteringRules': - response = µb.permanentURLFiltering.copyRules( - µb.sessionURLFiltering, + response = permanentURLFiltering.copyRules( + sessionURLFiltering, request.context, request.urls, request.type @@ -1539,7 +1552,7 @@ const onMessage = function(request, sender, callback) { break; case 'temporarilyWhitelistDocument': - µBlock.webRequest.strictBlockBypass(request.hostname); + webRequest.strictBlockBypass(request.hostname); break; default: @@ -1568,10 +1581,8 @@ vAPI.messaging.listen({ { // >>>>> start of local scope -const µb = µBlock; - const logCosmeticFilters = function(tabId, details) { - if ( µb.logger.enabled === false ) { return; } + if ( logger.enabled === false ) { return; } const filter = { source: 'cosmetic', raw: '' }; const fctxt = µb.filteringContext.duplicate(); @@ -1588,7 +1599,7 @@ const logCosmeticFilters = function(tabId, details) { }; const logCSPViolations = function(pageStore, request) { - if ( µb.logger.enabled === false || pageStore === null ) { + if ( logger.enabled === false || pageStore === null ) { return false; } if ( request.violations.length === 0 ) { @@ -1606,7 +1617,7 @@ const logCSPViolations = function(pageStore, request) { cspData = new Map(); const staticDirectives = - µb.staticNetFilteringEngine.matchAndFetchModifiers(fctxt, 'csp'); + staticNetFilteringEngine.matchAndFetchModifiers(fctxt, 'csp'); if ( staticDirectives !== undefined ) { for ( const directive of staticDirectives ) { if ( directive.result !== 1 ) { continue; } @@ -1691,7 +1702,7 @@ const onMessage = function(request, sender, callback) { switch ( request.what ) { case 'inlinescriptFound': - if ( µb.logger.enabled && pageStore !== null ) { + if ( logger.enabled && pageStore !== null ) { const fctxt = µb.filteringContext.duplicate(); fctxt.fromTabId(tabId) .setType('inline-script') diff --git a/src/js/pagestore.js b/src/js/pagestore.js index 64d14c2b4..5d9871cf0 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -23,14 +23,21 @@ /******************************************************************************/ +import contextMenu from './contextmenu.js'; +import logger from './logger.js'; +import staticNetFilteringEngine from './static-net-filtering.js'; +import µb from './background.js'; +import { redirectEngine } from './redirect-engine.js'; +import { sessionFirewall } from './dynamic-net-filtering.js'; +import { sessionSwitches } from './hnswitches.js'; +import { sessionURLFiltering } from './url-net-filtering.js'; + import { domainFromHostname, hostnameFromURI, isNetworkURI, } from './uri-utils.js'; -import µBlock from './background.js'; - /******************************************************************************* A PageRequestStore object is used to store net requests in two ways: @@ -42,10 +49,6 @@ To create a log of net requests /******************************************************************************/ -const µb = µBlock; - -/******************************************************************************/ - const NetFilteringResultCache = class { constructor() { this.init(); @@ -217,18 +220,18 @@ const FrameStore = class { } this._cosmeticFilteringBits = 0b11; { - const result = µb.staticNetFilteringEngine.matchRequestReverse( + const result = staticNetFilteringEngine.matchRequestReverse( 'specifichide', this.rawURL ); - if ( result !== 0 && µb.logger.enabled ) { - µBlock.filteringContext + if ( result !== 0 && logger.enabled ) { + µb.filteringContext .duplicate() .fromTabId(tabId) .setURL(this.rawURL) .setRealm('network') .setType('specifichide') - .setFilter(µb.staticNetFilteringEngine.toLogData()) + .setFilter(staticNetFilteringEngine.toLogData()) .toLogger(); } if ( result === 2 ) { @@ -236,18 +239,18 @@ const FrameStore = class { } } { - const result = µb.staticNetFilteringEngine.matchRequestReverse( + const result = staticNetFilteringEngine.matchRequestReverse( 'generichide', this.rawURL ); - if ( result !== 0 && µb.logger.enabled ) { - µBlock.filteringContext + if ( result !== 0 && logger.enabled ) { + µb.filteringContext .duplicate() .fromTabId(tabId) .setURL(this.rawURL) .setRealm('network') .setType('generichide') - .setFilter(µb.staticNetFilteringEngine.toLogData()) + .setFilter(staticNetFilteringEngine.toLogData()) .toLogger(); } if ( result === 2 ) { @@ -559,18 +562,18 @@ const PageStore = class { if ( this._noCosmeticFiltering === undefined ) { this._noCosmeticFiltering = this.getNetFilteringSwitch() === false; if ( this._noCosmeticFiltering === false ) { - this._noCosmeticFiltering = µb.sessionSwitches.evaluateZ( + this._noCosmeticFiltering = sessionSwitches.evaluateZ( 'no-cosmetic-filtering', this.tabHostname ) === true; - if ( this._noCosmeticFiltering && µb.logger.enabled ) { + if ( this._noCosmeticFiltering && logger.enabled ) { µb.filteringContext .duplicate() .fromTabId(this.tabId) .setURL(this.rawURL) .setRealm('cosmetic') .setType('dom') - .setFilter(µb.sessionSwitches.toLogData()) + .setFilter(sessionSwitches.toLogData()) .toLogger(); } } @@ -620,12 +623,12 @@ const PageStore = class { allFrames: true, runAt: 'document_idle', }); - µb.contextMenu.update(this.tabId); + contextMenu.update(this.tabId); } temporarilyAllowLargeMediaElements(state) { this.largeMediaCount = 0; - µb.contextMenu.update(this.tabId); + contextMenu.update(this.tabId); if ( state ) { this.allowLargeMediaElementsUntil = 0; this.allowLargeMediaElementsRegex = undefined; @@ -784,32 +787,32 @@ const PageStore = class { } const requestType = fctxt.type; - const loggerEnabled = µb.logger.enabled; + const loggerEnabled = logger.enabled; // Dynamic URL filtering. - let result = µb.sessionURLFiltering.evaluateZ( + let result = sessionURLFiltering.evaluateZ( fctxt.getTabHostname(), fctxt.url, requestType ); if ( result !== 0 && loggerEnabled ) { - fctxt.filter = µb.sessionURLFiltering.toLogData(); + fctxt.filter = sessionURLFiltering.toLogData(); } // Dynamic hostname/type filtering. if ( result === 0 && µb.userSettings.advancedUserEnabled ) { - result = µb.sessionFirewall.evaluateCellZY( + result = sessionFirewall.evaluateCellZY( fctxt.getTabHostname(), fctxt.getHostname(), requestType ); if ( result !== 0 && result !== 3 && loggerEnabled ) { - fctxt.filter = µb.sessionFirewall.toLogData(); + fctxt.filter = sessionFirewall.toLogData(); } } // Static filtering has lowest precedence. - const snfe = µb.staticNetFilteringEngine; + const snfe = staticNetFilteringEngine; if ( result === 0 || result === 3 ) { result = snfe.matchRequest(fctxt); if ( result !== 0 ) { @@ -873,19 +876,19 @@ const PageStore = class { if ( this.getNetFilteringSwitch(fctxt) === false ) { return 0; } - let result = µb.staticNetFilteringEngine.matchHeaders(fctxt, headers); + let result = staticNetFilteringEngine.matchHeaders(fctxt, headers); if ( result === 0 ) { return 0; } - const loggerEnabled = µb.logger.enabled; + const loggerEnabled = logger.enabled; if ( loggerEnabled ) { - fctxt.filter = µb.staticNetFilteringEngine.toLogData(); + fctxt.filter = staticNetFilteringEngine.toLogData(); } // Dynamic filtering allow rules // URL filtering if ( result === 1 && - µb.sessionURLFiltering.evaluateZ( + sessionURLFiltering.evaluateZ( fctxt.getTabHostname(), fctxt.url, fctxt.type @@ -893,14 +896,14 @@ const PageStore = class { ) { result = 2; if ( loggerEnabled ) { - fctxt.filter = µb.sessionURLFiltering.toLogData(); + fctxt.filter = sessionURLFiltering.toLogData(); } } // Hostname filtering if ( result === 1 && µb.userSettings.advancedUserEnabled && - µb.sessionFirewall.evaluateCellZY( + sessionFirewall.evaluateCellZY( fctxt.getTabHostname(), fctxt.getHostname(), fctxt.type @@ -908,7 +911,7 @@ const PageStore = class { ) { result = 2; if ( loggerEnabled ) { - fctxt.filter = µb.sessionFirewall.toLogData(); + fctxt.filter = sessionFirewall.toLogData(); } } @@ -916,24 +919,24 @@ const PageStore = class { } redirectBlockedRequest(fctxt) { - const directives = µb.staticNetFilteringEngine.redirectRequest( - µb.redirectEngine, + const directives = staticNetFilteringEngine.redirectRequest( + redirectEngine, fctxt ); if ( directives === undefined ) { return; } - if ( µb.logger.enabled !== true ) { return; } + if ( logger.enabled !== true ) { return; } fctxt.pushFilters(directives.map(a => a.logData())); if ( fctxt.redirectURL === undefined ) { return; } fctxt.pushFilter({ source: 'redirect', - raw: µb.redirectEngine.resourceNameRegister + raw: redirectEngine.resourceNameRegister }); } redirectNonBlockedRequest(fctxt) { - const directives = µb.staticNetFilteringEngine.filterQuery(fctxt); + const directives = staticNetFilteringEngine.filterQuery(fctxt); if ( directives === undefined ) { return; } - if ( µb.logger.enabled !== true ) { return; } + if ( logger.enabled !== true ) { return; } fctxt.pushFilters(directives.map(a => a.logData())); if ( fctxt.redirectURL === undefined ) { return; } fctxt.pushFilter({ @@ -944,13 +947,13 @@ const PageStore = class { filterCSPReport(fctxt) { if ( - µb.sessionSwitches.evaluateZ( + sessionSwitches.evaluateZ( 'no-csp-reports', fctxt.getHostname() ) ) { - if ( µb.logger.enabled ) { - fctxt.filter = µb.sessionSwitches.toLogData(); + if ( logger.enabled ) { + fctxt.filter = sessionSwitches.toLogData(); } return 1; } @@ -962,13 +965,13 @@ const PageStore = class { this.remoteFontCount += 1; } if ( - µb.sessionSwitches.evaluateZ( + sessionSwitches.evaluateZ( 'no-remote-fonts', fctxt.getTabHostname() ) !== false ) { - if ( µb.logger.enabled ) { - fctxt.filter = µb.sessionSwitches.toLogData(); + if ( logger.enabled ) { + fctxt.filter = sessionSwitches.toLogData(); } return 1; } @@ -982,15 +985,15 @@ const PageStore = class { } if ( netFiltering === false || - µb.sessionSwitches.evaluateZ( + sessionSwitches.evaluateZ( 'no-scripting', fctxt.getTabHostname() ) === false ) { return 0; } - if ( µb.logger.enabled ) { - fctxt.filter = µb.sessionSwitches.toLogData(); + if ( logger.enabled ) { + fctxt.filter = sessionSwitches.toLogData(); } return 1; } @@ -1019,7 +1022,7 @@ const PageStore = class { return 0; } if ( - µb.sessionSwitches.evaluateZ( + sessionSwitches.evaluateZ( 'no-large-media', fctxt.getTabHostname() ) !== true @@ -1039,8 +1042,8 @@ const PageStore = class { }, 500); } - if ( µb.logger.enabled ) { - fctxt.filter = µb.sessionSwitches.toLogData(); + if ( logger.enabled ) { + fctxt.filter = sessionSwitches.toLogData(); } return 1; @@ -1069,14 +1072,14 @@ const PageStore = class { } } if ( exceptCname === undefined ) { - const result = µb.staticNetFilteringEngine.matchRequestReverse( + const result = staticNetFilteringEngine.matchRequestReverse( 'cname', frameStore instanceof Object ? frameStore.rawURL : fctxt.getDocOrigin() ); exceptCname = result === 2 - ? µb.staticNetFilteringEngine.toLogData() + ? staticNetFilteringEngine.toLogData() : false; if ( frameStore instanceof Object ) { frameStore.exceptCname = exceptCname; @@ -1128,6 +1131,6 @@ PageStore.prototype.collapsibleResources = new Set([ PageStore.junkyard = []; PageStore.junkyardMax = 10; -µb.PageStore = PageStore; - /******************************************************************************/ + +export { PageStore }; diff --git a/src/js/redirect-engine.js b/src/js/redirect-engine.js index a07fddbe6..58a6ec467 100644 --- a/src/js/redirect-engine.js +++ b/src/js/redirect-engine.js @@ -23,8 +23,9 @@ /******************************************************************************/ +import io from './assets.js'; +import µb from './background.js'; import { LineIterator } from './text-iterators.js'; -import µBlock from './background.js'; /******************************************************************************/ @@ -408,7 +409,7 @@ RedirectEngine.prototype.resourcesFromString = function(text) { // No more data, add the resource. const name = this.aliases.get(fields[0]) || fields[0]; const mime = fields[1]; - const content = µBlock.orphanizeString( + const content = µb.orphanizeString( fields.slice(2).join(encoded ? '' : '\n') ); this.resources.set( @@ -436,14 +437,11 @@ const removeTopCommentBlock = function(text) { /******************************************************************************/ RedirectEngine.prototype.loadBuiltinResources = function() { - // TODO: remove once usage of uBO 1.20.4 is widespread. - µBlock.assets.remove('ublock-resources'); - this.resources = new Map(); this.aliases = new Map(); const fetches = [ - µBlock.assets.fetchText( + io.fetchText( '/assets/resources/scriptlets.js' ).then(result => { const content = result.content; @@ -505,7 +503,7 @@ RedirectEngine.prototype.loadBuiltinResources = function() { continue; } fetches.push( - µBlock.assets.fetch( + io.fetch( `/web_accessible_resources/${name}?secret=${vAPI.warSecret()}`, { responseType: details.data } ).then( @@ -547,7 +545,7 @@ RedirectEngine.prototype.getResourceDetails = function() { const resourcesSelfieVersion = 5; RedirectEngine.prototype.selfieFromResources = function() { - µBlock.assets.put( + io.put( 'compiled/redirectEngine/resources', JSON.stringify({ version: resourcesSelfieVersion, @@ -558,7 +556,7 @@ RedirectEngine.prototype.selfieFromResources = function() { }; RedirectEngine.prototype.resourcesFromSelfie = async function() { - const result = await µBlock.assets.get('compiled/redirectEngine/resources'); + const result = await io.get('compiled/redirectEngine/resources'); let selfie; try { selfie = JSON.parse(result.content); @@ -580,13 +578,13 @@ RedirectEngine.prototype.resourcesFromSelfie = async function() { }; RedirectEngine.prototype.invalidateResourcesSelfie = function() { - µBlock.assets.remove('compiled/redirectEngine/resources'); + io.remove('compiled/redirectEngine/resources'); }; /******************************************************************************/ -// Export +const redirectEngine = new RedirectEngine(); -µBlock.redirectEngine = new RedirectEngine(); +export { redirectEngine }; /******************************************************************************/ diff --git a/src/js/reverselookup-worker.js b/src/js/reverselookup-worker.js new file mode 100644 index 000000000..0e4cf624f --- /dev/null +++ b/src/js/reverselookup-worker.js @@ -0,0 +1,293 @@ +/******************************************************************************* + + 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'; + +/******************************************************************************/ + +{ +// >>>>> start of local scope + +/******************************************************************************/ + +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, 100, 101); + 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, 200, 1000), + 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 + // Response header 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; + } +}; + +/******************************************************************************/ + +// <<<<< end of local scope +} + +/******************************************************************************/ diff --git a/src/js/reverselookup.js b/src/js/reverselookup.js index 8549287a5..aa3bf01ae 100644 --- a/src/js/reverselookup.js +++ b/src/js/reverselookup.js @@ -23,462 +23,192 @@ /******************************************************************************/ -(( ) => { -// >>>>> start of local scope +import staticNetFilteringEngine from './static-net-filtering.js'; +import µb from './background.js'; +import { CompiledListWriter } from './static-filtering-io.js'; +import { StaticFilteringParser } from './static-filtering-parser.js'; + +import { + domainFromHostname, + hostnameFromURI, +} from './uri-utils.js'; /******************************************************************************/ -// Worker context +const workerTTL = 5 * 60 * 1000; +const pendingResponses = new Map(); -if ( - self.WorkerGlobalScope instanceof Object && - self instanceof self.WorkerGlobalScope -) { - const reBlockStart = /^#block-start-(\d+)\n/gm; - let listEntries = Object.create(null); +let worker = null; +let workerTTLTimer; +let needLists = true; +let messageId = 1; - 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'); - }; +const onWorkerMessage = function(e) { + const msg = e.data; + const resolver = pendingResponses.get(msg.id); + pendingResponses.delete(msg.id); + resolver(msg.response); +}; - // https://github.com/MajkiIT/polish-ads-filter/issues/14768#issuecomment-536006312 - // Avoid reporting badfilter-ed filters. +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 fromNetFilter = function(details) { - const lists = []; - const compiledFilter = details.compiledFilter; +const initWorker = function() { + if ( worker === null ) { + worker = new Worker('js/reverselookup-worker.js'); + worker.onmessage = onWorkerMessage; + } - for ( const assetKey in listEntries ) { - const entry = listEntries[assetKey]; - if ( entry === undefined ) { continue; } - const content = extractBlocks(entry.content, 100, 101); - 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; - } - } + // The worker will be shutdown after n minutes without being used. + if ( workerTTLTimer !== undefined ) { + clearTimeout(workerTTLTimer); + } + workerTTLTimer = vAPI.setTimeout(stopWorker, workerTTL); - const response = {}; - response[details.rawFilter] = lists; + if ( needLists === false ) { + return Promise.resolve(); + } + needLists = false; - self.postMessage({ id: details.id, response }); - }; + const entries = new Map(); - // 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 onListLoaded = function(details) { + const entry = entries.get(details.assetKey); - 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, 200, 1000), - 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 - // Response header 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.CompiledListWriter(); - const parser = new µb.StaticFilteringParser(); - parser.setMaxTokenLength(µb.staticNetFilteringEngine.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.hostnameFromURI(details.url); + // https://github.com/gorhill/uBlock/issues/536 + // Use assetKey when there is no filter list title. worker.postMessage({ - what: 'fromCosmeticFilter', - id: id, - domain: µBlock.domainFromHostname(hostname), - hostname: hostname, - ignoreGeneric: - µBlock.staticNetFilteringEngine.matchRequestReverse( - 'generichide', - details.url - ) === 2, - ignoreSpecific: - µBlock.staticNetFilteringEngine.matchRequestReverse( - 'specifichide', - details.url - ) === 2, - rawFilter: details.rawFilter - }); - - return new Promise(resolve => { - pendingResponses.set(id, resolve); + what: 'setList', + details: { + assetKey: details.assetKey, + title: entry.title || details.assetKey, + supportURL: entry.supportURL, + content: details.content + } }); }; - // This tells the worker that filter lists may have changed. + 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 resetLists = function() { - needLists = true; - if ( worker === null ) { return; } - worker.postMessage({ what: 'resetLists' }); - }; + const promises = []; + for ( const listKey of entries.keys() ) { + promises.push( + µb.getCompiledFilterList(listKey).then(details => { + onListLoaded(details); + }) + ); + } + return Promise.all(promises); +}; - µBlock.staticFilteringReverseLookup = { - fromNetFilter, - fromCosmeticFilter, - resetLists, - shutdown: stopWorker - }; -} +const fromNetFilter = async function(rawFilter) { + if ( typeof rawFilter !== 'string' || rawFilter === '' ) { return; } + + const writer = new CompiledListWriter(); + const parser = new StaticFilteringParser(); + parser.setMaxTokenLength(staticNetFilteringEngine.MAX_TOKEN_LENGTH); + parser.analyze(rawFilter); + + if ( 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 = hostnameFromURI(details.url); + + worker.postMessage({ + what: 'fromCosmeticFilter', + id: id, + domain: domainFromHostname(hostname), + hostname: hostname, + ignoreGeneric: + staticNetFilteringEngine.matchRequestReverse( + 'generichide', + details.url + ) === 2, + ignoreSpecific: + staticNetFilteringEngine.matchRequestReverse( + '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' }); +}; /******************************************************************************/ -// <<<<< end of local scope -})(); +const staticFilteringReverseLookup = { + fromNetFilter, + fromCosmeticFilter, + resetLists, + shutdown: stopWorker +}; + +export default staticFilteringReverseLookup; /******************************************************************************/ diff --git a/src/js/scriptlet-filtering.js b/src/js/scriptlet-filtering.js index 5da7a0b16..73ffca877 100644 --- a/src/js/scriptlet-filtering.js +++ b/src/js/scriptlet-filtering.js @@ -23,28 +23,35 @@ /******************************************************************************/ +import logger from './logger.js'; +import µb from './background.js'; +import { redirectEngine } from './redirect-engine.js'; +import { sessionFirewall } from './dynamic-net-filtering.js'; + +import { + StaticExtFilteringHostnameDB, + StaticExtFilteringSessionDB, +} from './static-ext-filtering-db.js'; + import { domainFromHostname, entityFromDomain, hostnameFromURI, } from './uri-utils.js'; -import µBlock from './background.js'; - /******************************************************************************/ -const µb = µBlock; const duplicates = new Set(); const scriptletCache = new µb.MRUCache(32); const reEscapeScriptArg = /[\\'"]/g; -const scriptletDB = new µb.staticExtFilteringEngine.HostnameBasedDB(1); -const sessionScriptletDB = new µb.staticExtFilteringEngine.SessionDB(); +const scriptletDB = new StaticExtFilteringHostnameDB(1); +const sessionScriptletDB = new StaticExtFilteringSessionDB(); let acceptedCount = 0; let discardedCount = 0; -const api = { +const scriptletFilteringEngine = { get acceptedCount() { return acceptedCount; }, @@ -132,7 +139,7 @@ const normalizeRawFilter = function(rawFilter) { if ( end === -1 ) { end = rawEnd; } const token = rawToken.slice(0, end).trim(); const alias = token.endsWith('.js') ? token.slice(0, -3) : token; - let normalized = µb.redirectEngine.aliases.get(`${alias}.js`); + let normalized = redirectEngine.aliases.get(`${alias}.js`); normalized = normalized === undefined ? alias : normalized.slice(0, -3); @@ -212,7 +219,7 @@ const patchScriptlet = function(content, args) { }; const logOne = function(isException, token, details) { - µBlock.filteringContext + µb.filteringContext .duplicate() .fromTabId(details.tabId) .setRealm('extended') @@ -226,19 +233,19 @@ const logOne = function(isException, token, details) { .toLogger(); }; -api.reset = function() { +scriptletFilteringEngine.reset = function() { scriptletDB.clear(); duplicates.clear(); acceptedCount = 0; discardedCount = 0; }; -api.freeze = function() { +scriptletFilteringEngine.freeze = function() { duplicates.clear(); scriptletDB.collectGarbage(); }; -api.compile = function(parser, writer) { +scriptletFilteringEngine.compile = function(parser, writer) { writer.select(µb.compiledScriptletSection); // Only exception filters are allowed to be global. @@ -272,7 +279,7 @@ api.compile = function(parser, writer) { } }; -api.compileTemporary = function(parser) { +scriptletFilteringEngine.compileTemporary = function(parser) { return { session: sessionScriptletDB, selector: parser.result.compiled, @@ -284,7 +291,7 @@ api.compileTemporary = function(parser) { // ^ ^ // 4 -1 -api.fromCompiledContent = function(reader) { +scriptletFilteringEngine.fromCompiledContent = function(reader) { reader.select(µb.compiledScriptletSection); while ( reader.next() ) { @@ -301,7 +308,7 @@ api.fromCompiledContent = function(reader) { } }; -api.getSession = function() { +scriptletFilteringEngine.getSession = function() { return sessionScriptletDB; }; @@ -309,12 +316,9 @@ const $scriptlets = new Set(); const $exceptions = new Set(); const $scriptletToCodeMap = new Map(); -api.retrieve = function(request) { +scriptletFilteringEngine.retrieve = function(request) { if ( scriptletDB.size === 0 ) { return; } - const reng = µb.redirectEngine; - if ( !reng ) { return; } - const hostname = request.hostname; $scriptlets.clear(); @@ -334,16 +338,14 @@ api.retrieve = function(request) { // Do not inject scriptlets if the site is under an `allow` rule. if ( µb.userSettings.advancedUserEnabled && - µb.sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 + sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 ) { return; } - const loggerEnabled = µb.logger.enabled; - // Wholly disable scriptlet injection? if ( $exceptions.has('') ) { - if ( loggerEnabled ) { + if ( logger.enabled ) { logOne(true, '', request); } return; @@ -351,7 +353,7 @@ api.retrieve = function(request) { $scriptletToCodeMap.clear(); for ( const rawToken of $scriptlets ) { - lookupScriptlet(rawToken, reng, $scriptletToCodeMap); + lookupScriptlet(rawToken, redirectEngine, $scriptletToCodeMap); } if ( $scriptletToCodeMap.size === 0 ) { return; } @@ -362,7 +364,7 @@ api.retrieve = function(request) { if ( isException === false ) { out.push(code); } - if ( loggerEnabled ) { + if ( logger.enabled ) { logOne(isException, rawToken, request); } } @@ -390,11 +392,11 @@ api.retrieve = function(request) { return out.join('\n'); }; -api.hasScriptlet = function(hostname, exceptionBit, scriptlet) { +scriptletFilteringEngine.hasScriptlet = function(hostname, exceptionBit, scriptlet) { return scriptletDB.hasStr(hostname, exceptionBit, scriptlet); }; -api.injectNow = function(details) { +scriptletFilteringEngine.injectNow = function(details) { if ( typeof details.frameId !== 'number' ) { return; } const request = { tabId: details.tabId, @@ -406,7 +408,7 @@ api.injectNow = function(details) { }; request.domain = domainFromHostname(request.hostname); request.entity = entityFromDomain(request.domain); - const scriptlets = µb.scriptletFilteringEngine.retrieve(request); + const scriptlets = this.retrieve(request); if ( scriptlets === undefined ) { return; } let code = contentscriptCode.assemble(request.hostname, scriptlets); if ( µb.hiddenSettings.debugScriptletInjector ) { @@ -420,21 +422,21 @@ api.injectNow = function(details) { }); }; -api.toSelfie = function() { +scriptletFilteringEngine.toSelfie = function() { return scriptletDB.toSelfie(); }; -api.fromSelfie = function(selfie) { +scriptletFilteringEngine.fromSelfie = function(selfie) { scriptletDB.fromSelfie(selfie); }; -api.benchmark = async function() { +scriptletFilteringEngine.benchmark = async function() { const requests = await µb.loadBenchmarkDataset(); if ( Array.isArray(requests) === false || requests.length === 0 ) { - log.print('No requests found to benchmark'); + console.info('No requests found to benchmark'); return; } - log.print('Benchmarking scriptletFilteringEngine.retrieve()...'); + console.info('Benchmarking scriptletFilteringEngine.retrieve()...'); const details = { domain: '', entity: '', @@ -456,14 +458,12 @@ api.benchmark = async function() { } const t1 = self.performance.now(); const dur = t1 - t0; - log.print(`Evaluated ${count} requests in ${dur.toFixed(0)} ms`); - log.print(`\tAverage: ${(dur / count).toFixed(3)} ms per request`); + console.info(`Evaluated ${count} requests in ${dur.toFixed(0)} ms`); + console.info(`\tAverage: ${(dur / count).toFixed(3)} ms per request`); }; /******************************************************************************/ -// Export - -µBlock.scriptletFilteringEngine = api; +export default scriptletFilteringEngine; /******************************************************************************/ diff --git a/src/js/start.js b/src/js/start.js index 45b185435..e3514f7c8 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -23,7 +23,32 @@ /******************************************************************************/ -import µBlock from './background.js'; +import cacheStorage from './cachestorage.js'; +import contextMenu from './contextmenu.js'; +import io from './assets.js'; +import lz4Codec from './lz4.js'; +import staticExtFilteringEngine from './static-ext-filtering.js'; +import staticFilteringReverseLookup from './reverselookup.js'; +import staticNetFilteringEngine from './static-net-filtering.js'; +import µb from './background.js'; +import { redirectEngine } from './redirect-engine.js'; +import { ubolog } from './console.js'; +import { webRequest } from './traffic.js'; + +import { + permanentFirewall, + sessionFirewall, +} from './dynamic-net-filtering.js'; + +import { + permanentSwitches, + sessionSwitches, +} from './hnswitches.js'; + +import { + permanentURLFiltering, + sessionURLFiltering, +} from './url-net-filtering.js'; /******************************************************************************/ @@ -32,21 +57,19 @@ import µBlock from './background.js'; (async ( ) => { // >>>>> start of private scope -const µb = µBlock; - /******************************************************************************/ vAPI.app.onShutdown = function() { - µb.staticFilteringReverseLookup.shutdown(); - µb.assets.updateStop(); - µb.staticNetFilteringEngine.reset(); - µb.staticExtFilteringEngine.reset(); - µb.sessionFirewall.reset(); - µb.permanentFirewall.reset(); - µb.sessionURLFiltering.reset(); - µb.permanentURLFiltering.reset(); - µb.sessionSwitches.reset(); - µb.permanentSwitches.reset(); + staticFilteringReverseLookup.shutdown(); + io.updateStop(); + staticNetFilteringEngine.reset(); + staticExtFilteringEngine.reset(); + sessionFirewall.reset(); + permanentFirewall.reset(); + sessionURLFiltering.reset(); + permanentURLFiltering.reset(); + sessionSwitches.reset(); + permanentSwitches.reset(); }; /******************************************************************************/ @@ -116,7 +139,7 @@ const onVersionReady = function(lastVersion) { // Since built-in resources may have changed since last version, we // force a reload of all resources. - µb.redirectEngine.invalidateResourcesSelfie(); + redirectEngine.invalidateResourcesSelfie(); // https://github.com/LiCybora/NanoDefenderFirefox/issues/196 // Toggle on the blocking of CSP reports by default for Firefox. @@ -124,8 +147,8 @@ const onVersionReady = function(lastVersion) { lastVersionInt <= 1031003011 && vAPI.webextFlavor.soup.has('firefox') ) { - µb.sessionSwitches.toggle('no-csp-reports', '*', 1); - µb.permanentSwitches.toggle('no-csp-reports', '*', 1); + sessionSwitches.toggle('no-csp-reports', '*', 1); + permanentSwitches.toggle('no-csp-reports', '*', 1); µb.saveHostnameSwitches(); } }; @@ -225,7 +248,7 @@ const onCacheSettingsReady = async function(fetched) { } if ( µb.selfieIsInvalid ) { µb.selfieManager.destroy(); - µb.cacheStorage.set(µb.systemSettings); + cacheStorage.set(µb.systemSettings); } }; @@ -243,12 +266,12 @@ const onFirstFetchReady = function(fetched, adminExtra) { fromFetch(µb.localSettings, fetched); fromFetch(µb.restoreBackupSettings, fetched); - µb.permanentFirewall.fromString(fetched.dynamicFilteringString); - µb.sessionFirewall.assign(µb.permanentFirewall); - µb.permanentURLFiltering.fromString(fetched.urlFilteringString); - µb.sessionURLFiltering.assign(µb.permanentURLFiltering); - µb.permanentSwitches.fromString(fetched.hostnameSwitchesString); - µb.sessionSwitches.assign(µb.permanentSwitches); + permanentFirewall.fromString(fetched.dynamicFilteringString); + sessionFirewall.assign(permanentFirewall); + permanentURLFiltering.fromString(fetched.urlFilteringString); + sessionURLFiltering.assign(permanentURLFiltering); + permanentSwitches.fromString(fetched.hostnameSwitchesString); + sessionSwitches.assign(permanentSwitches); onNetWhitelistReady(fetched.netWhitelist, adminExtra); onVersionReady(fetched.version); @@ -307,10 +330,10 @@ const createDefaultProps = function() { try { // https://github.com/gorhill/uBlock/issues/531 await µb.restoreAdminSettings(); - log.info(`Admin settings ready ${Date.now()-vAPI.T0} ms after launch`); + ubolog(`Admin settings ready ${Date.now()-vAPI.T0} ms after launch`); await µb.loadHiddenSettings(); - log.info(`Hidden settings ready ${Date.now()-vAPI.T0} ms after launch`); + ubolog(`Hidden settings ready ${Date.now()-vAPI.T0} ms after launch`); // Maybe override current network listener suspend state if ( µb.hiddenSettings.suspendTabsUntilReady === 'no' ) { @@ -320,42 +343,42 @@ try { } if ( µb.hiddenSettings.disableWebAssembly !== true ) { - µb.staticNetFilteringEngine.enableWASM('/js').then(( ) => { - log.info(`WASM modules ready ${Date.now()-vAPI.T0} ms after launch`); + staticNetFilteringEngine.enableWASM('/js').then(( ) => { + ubolog(`WASM modules ready ${Date.now()-vAPI.T0} ms after launch`); }); } - const cacheBackend = await µb.cacheStorage.select( + const cacheBackend = await cacheStorage.select( µb.hiddenSettings.cacheStorageAPI ); - log.info(`Backend storage for cache will be ${cacheBackend}`); + ubolog(`Backend storage for cache will be ${cacheBackend}`); const adminExtra = await vAPI.adminStorage.get('toAdd'); - log.info(`Extra admin settings ready ${Date.now()-vAPI.T0} ms after launch`); + ubolog(`Extra admin settings ready ${Date.now()-vAPI.T0} ms after launch`); // https://github.com/uBlockOrigin/uBlock-issues/issues/1365 // Wait for onCacheSettingsReady() to be fully ready. const [ , , lastVersion ] = await Promise.all([ µb.loadSelectedFilterLists().then(( ) => { - log.info(`List selection ready ${Date.now()-vAPI.T0} ms after launch`); + ubolog(`List selection ready ${Date.now()-vAPI.T0} ms after launch`); }), - µb.cacheStorage.get( + cacheStorage.get( { compiledMagic: 0, selfieMagic: 0 } ).then(fetched => { - log.info(`Cache magic numbers ready ${Date.now()-vAPI.T0} ms after launch`); + ubolog(`Cache magic numbers ready ${Date.now()-vAPI.T0} ms after launch`); onCacheSettingsReady(fetched); }), vAPI.storage.get(createDefaultProps()).then(fetched => { - log.info(`First fetch ready ${Date.now()-vAPI.T0} ms after launch`); + ubolog(`First fetch ready ${Date.now()-vAPI.T0} ms after launch`); onFirstFetchReady(fetched, adminExtra); return fetched.version; }), µb.loadUserSettings().then(fetched => { - log.info(`User settings ready ${Date.now()-vAPI.T0} ms after launch`); + ubolog(`User settings ready ${Date.now()-vAPI.T0} ms after launch`); onUserSettingsReady(fetched); }), µb.loadPublicSuffixList().then(( ) => { - log.info(`PSL ready ${Date.now()-vAPI.T0} ms after launch`); + ubolog(`PSL ready ${Date.now()-vAPI.T0} ms after launch`); }), ]); @@ -369,7 +392,7 @@ try { } // Prime the filtering engines before first use. -µb.staticNetFilteringEngine.prime(); +staticNetFilteringEngine.prime(); // https://github.com/uBlockOrigin/uBlock-issues/issues/817#issuecomment-565730122 // Still try to load filter lists regardless of whether a serious error @@ -378,7 +401,7 @@ let selfieIsValid = false; try { selfieIsValid = await µb.selfieManager.load(); if ( selfieIsValid === true ) { - log.info(`Selfie ready ${Date.now()-vAPI.T0} ms after launch`); + ubolog(`Selfie ready ${Date.now()-vAPI.T0} ms after launch`); } } catch (ex) { console.trace(ex); @@ -386,7 +409,7 @@ try { if ( selfieIsValid !== true ) { try { await µb.loadFilterLists(); - log.info(`Filter lists ready ${Date.now()-vAPI.T0} ms after launch`); + ubolog(`Filter lists ready ${Date.now()-vAPI.T0} ms after launch`); } catch (ex) { console.trace(ex); } @@ -399,21 +422,21 @@ if ( selfieIsValid !== true ) { µb.readyToFilter = true; // Start network observers. -µb.webRequest.start(); +webRequest.start(); // Ensure that the resources allocated for decompression purpose (likely // large buffers) are garbage-collectable immediately after launch. // Otherwise I have observed that it may take quite a while before the // garbage collection of these resources kicks in. Relinquishing as soon // as possible ensure minimal memory usage baseline. -µb.lz4Codec.relinquish(); +lz4Codec.relinquish(); // Initialize internal state with maybe already existing tabs. initializeTabs(); // https://github.com/chrisaljoudi/uBlock/issues/184 // Check for updates not too far in the future. -µb.assets.addObserver(µb.assetObserver.bind(µb)); +io.addObserver(µb.assetObserver.bind(µb)); µb.scheduleAssetUpdater( µb.userSettings.autoUpdate ? µb.hiddenSettings.autoUpdateDelayAfterLaunch * 1000 @@ -422,7 +445,7 @@ initializeTabs(); // Force an update of the context menu according to the currently // active tab. -µb.contextMenu.update(); +contextMenu.update(); // Maybe install non-default popup document, or automatically select // default UI according to platform. @@ -455,7 +478,7 @@ browser.runtime.onUpdateAvailable.addListener(details => { } }); -log.info(`All ready ${Date.now()-vAPI.T0} ms after launch`); +ubolog(`All ready ${Date.now()-vAPI.T0} ms after launch`); // <<<<< end of private scope })(); diff --git a/src/js/static-ext-filtering-db.js b/src/js/static-ext-filtering-db.js new file mode 100644 index 000000000..a118485b6 --- /dev/null +++ b/src/js/static-ext-filtering-db.js @@ -0,0 +1,224 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2017-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'; + +/******************************************************************************/ + +const StaticExtFilteringHostnameDB = class { + constructor(nBits, selfie = undefined) { + this.nBits = nBits; + this.timer = undefined; + this.strToIdMap = new Map(); + this.hostnameToSlotIdMap = new Map(); + // Array of integer pairs + this.hostnameSlots = []; + // Array of strings (selectors and pseudo-selectors) + this.strSlots = []; + this.size = 0; + if ( selfie !== undefined ) { + this.fromSelfie(selfie); + } + } + + store(hn, bits, s) { + this.size += 1; + let iStr = this.strToIdMap.get(s); + if ( iStr === undefined ) { + iStr = this.strSlots.length; + this.strSlots.push(s); + this.strToIdMap.set(s, iStr); + if ( this.timer === undefined ) { + this.collectGarbage(true); + } + } + const strId = iStr << this.nBits | bits; + let iHn = this.hostnameToSlotIdMap.get(hn); + if ( iHn === undefined ) { + this.hostnameToSlotIdMap.set(hn, this.hostnameSlots.length); + this.hostnameSlots.push(strId, 0); + return; + } + // Add as last item. + while ( this.hostnameSlots[iHn+1] !== 0 ) { + iHn = this.hostnameSlots[iHn+1]; + } + this.hostnameSlots[iHn+1] = this.hostnameSlots.length; + this.hostnameSlots.push(strId, 0); + } + + clear() { + this.hostnameToSlotIdMap.clear(); + this.hostnameSlots.length = 0; + this.strSlots.length = 0; + this.strToIdMap.clear(); + this.size = 0; + } + + collectGarbage(later = false) { + if ( later === false ) { + if ( this.timer !== undefined ) { + self.cancelIdleCallback(this.timer); + this.timer = undefined; + } + this.strToIdMap.clear(); + return; + } + if ( this.timer !== undefined ) { return; } + this.timer = self.requestIdleCallback( + ( ) => { + this.timer = undefined; + this.strToIdMap.clear(); + }, + { timeout: 5000 } + ); + } + + // modifiers = 1: return only specific items + // modifiers = 2: return only generic items + // + retrieve(hostname, out, modifiers = 0) { + if ( modifiers === 2 ) { + hostname = ''; + } + const mask = out.length - 1; // out.length must be power of two + for (;;) { + let iHn = this.hostnameToSlotIdMap.get(hostname); + if ( iHn !== undefined ) { + do { + const strId = this.hostnameSlots[iHn+0]; + out[strId & mask].add( + this.strSlots[strId >>> this.nBits] + ); + iHn = this.hostnameSlots[iHn+1]; + } while ( iHn !== 0 ); + } + if ( hostname === '' ) { break; } + const pos = hostname.indexOf('.'); + if ( pos === -1 ) { + if ( modifiers === 1 ) { break; } + hostname = ''; + } else { + hostname = hostname.slice(pos + 1); + } + } + } + + hasStr(hostname, exceptionBit, value) { + let found = false; + for (;;) { + let iHn = this.hostnameToSlotIdMap.get(hostname); + if ( iHn !== undefined ) { + do { + const strId = this.hostnameSlots[iHn+0]; + const str = this.strSlots[strId >>> this.nBits]; + if ( (strId & exceptionBit) !== 0 ) { + if ( str === value || str === '' ) { return false; } + } + if ( str === value ) { found = true; } + iHn = this.hostnameSlots[iHn+1]; + } while ( iHn !== 0 ); + } + if ( hostname === '' ) { break; } + const pos = hostname.indexOf('.'); + if ( pos !== -1 ) { + hostname = hostname.slice(pos + 1); + } else if ( hostname !== '*' ) { + hostname = '*'; + } else { + hostname = ''; + } + } + return found; + } + + toSelfie() { + return { + hostnameToSlotIdMap: Array.from(this.hostnameToSlotIdMap), + hostnameSlots: this.hostnameSlots, + strSlots: this.strSlots, + size: this.size + }; + } + + fromSelfie(selfie) { + if ( selfie === undefined ) { return; } + this.hostnameToSlotIdMap = new Map(selfie.hostnameToSlotIdMap); + this.hostnameSlots = selfie.hostnameSlots; + this.strSlots = selfie.strSlots; + this.size = selfie.size; + } +}; + +/******************************************************************************/ + +const StaticExtFilteringSessionDB = class { + constructor() { + this.db = new Map(); + } + compile(s) { + return s; + } + add(bits, s) { + const bucket = this.db.get(bits); + if ( bucket === undefined ) { + this.db.set(bits, new Set([ s ])); + } else { + bucket.add(s); + } + } + remove(bits, s) { + const bucket = this.db.get(bits); + if ( bucket === undefined ) { return; } + bucket.delete(s); + if ( bucket.size !== 0 ) { return; } + this.db.delete(bits); + } + retrieve(out) { + const mask = out.length - 1; + for ( const [ bits, bucket ] of this.db ) { + const i = bits & mask; + if ( out[i] instanceof Object === false ) { continue; } + for ( const s of bucket ) { + out[i].add(s); + } + } + } + has(bits, s) { + const selectors = this.db.get(bits); + return selectors !== undefined && selectors.has(s); + } + clear() { + this.db.clear(); + } + get isNotEmpty() { + return this.db.size !== 0; + } +}; + +/******************************************************************************/ + +export { + StaticExtFilteringHostnameDB, + StaticExtFilteringSessionDB, +}; + +/******************************************************************************/ diff --git a/src/js/static-ext-filtering.js b/src/js/static-ext-filtering.js index 04b86795c..d6bc851b2 100644 --- a/src/js/static-ext-filtering.js +++ b/src/js/static-ext-filtering.js @@ -23,7 +23,12 @@ /******************************************************************************/ -import µBlock from './background.js'; +import cosmeticFilteringEngine from './cosmetic-filtering.js'; +import htmlFilteringEngine from './html-filtering.js'; +import httpheaderFilteringEngine from './httpheader-filtering.js'; +import io from './assets.js'; +import logger from './logger.js'; +import scriptletFilteringEngine from './scriptlet-filtering.js'; /******************************************************************************* @@ -52,244 +57,49 @@ import µBlock from './background.js'; **/ -const µb = µBlock; - //-------------------------------------------------------------------------- // Public API //-------------------------------------------------------------------------- -const api = { +const staticExtFilteringEngine = { get acceptedCount() { - return µb.cosmeticFilteringEngine.acceptedCount + - µb.scriptletFilteringEngine.acceptedCount + - µb.httpheaderFilteringEngine.acceptedCount + - µb.htmlFilteringEngine.acceptedCount; + return cosmeticFilteringEngine.acceptedCount + + scriptletFilteringEngine.acceptedCount + + httpheaderFilteringEngine.acceptedCount + + htmlFilteringEngine.acceptedCount; }, get discardedCount() { - return µb.cosmeticFilteringEngine.discardedCount + - µb.scriptletFilteringEngine.discardedCount + - µb.httpheaderFilteringEngine.discardedCount + - µb.htmlFilteringEngine.discardedCount; + return cosmeticFilteringEngine.discardedCount + + scriptletFilteringEngine.discardedCount + + httpheaderFilteringEngine.discardedCount + + htmlFilteringEngine.discardedCount; }, }; -//-------------------------------------------------------------------------- -// Public classes -//-------------------------------------------------------------------------- - -api.HostnameBasedDB = class { - constructor(nBits, selfie = undefined) { - this.nBits = nBits; - this.timer = undefined; - this.strToIdMap = new Map(); - this.hostnameToSlotIdMap = new Map(); - // Array of integer pairs - this.hostnameSlots = []; - // Array of strings (selectors and pseudo-selectors) - this.strSlots = []; - this.size = 0; - if ( selfie !== undefined ) { - this.fromSelfie(selfie); - } - } - - store(hn, bits, s) { - this.size += 1; - let iStr = this.strToIdMap.get(s); - if ( iStr === undefined ) { - iStr = this.strSlots.length; - this.strSlots.push(s); - this.strToIdMap.set(s, iStr); - if ( this.timer === undefined ) { - this.collectGarbage(true); - } - } - const strId = iStr << this.nBits | bits; - let iHn = this.hostnameToSlotIdMap.get(hn); - if ( iHn === undefined ) { - this.hostnameToSlotIdMap.set(hn, this.hostnameSlots.length); - this.hostnameSlots.push(strId, 0); - return; - } - // Add as last item. - while ( this.hostnameSlots[iHn+1] !== 0 ) { - iHn = this.hostnameSlots[iHn+1]; - } - this.hostnameSlots[iHn+1] = this.hostnameSlots.length; - this.hostnameSlots.push(strId, 0); - } - - clear() { - this.hostnameToSlotIdMap.clear(); - this.hostnameSlots.length = 0; - this.strSlots.length = 0; - this.strToIdMap.clear(); - this.size = 0; - } - - collectGarbage(later = false) { - if ( later === false ) { - if ( this.timer !== undefined ) { - self.cancelIdleCallback(this.timer); - this.timer = undefined; - } - this.strToIdMap.clear(); - return; - } - if ( this.timer !== undefined ) { return; } - this.timer = self.requestIdleCallback( - ( ) => { - this.timer = undefined; - this.strToIdMap.clear(); - }, - { timeout: 5000 } - ); - } - - // modifiers = 1: return only specific items - // modifiers = 2: return only generic items - // - retrieve(hostname, out, modifiers = 0) { - if ( modifiers === 2 ) { - hostname = ''; - } - const mask = out.length - 1; // out.length must be power of two - for (;;) { - let iHn = this.hostnameToSlotIdMap.get(hostname); - if ( iHn !== undefined ) { - do { - const strId = this.hostnameSlots[iHn+0]; - out[strId & mask].add( - this.strSlots[strId >>> this.nBits] - ); - iHn = this.hostnameSlots[iHn+1]; - } while ( iHn !== 0 ); - } - if ( hostname === '' ) { break; } - const pos = hostname.indexOf('.'); - if ( pos === -1 ) { - if ( modifiers === 1 ) { break; } - hostname = ''; - } else { - hostname = hostname.slice(pos + 1); - } - } - } - - hasStr(hostname, exceptionBit, value) { - let found = false; - for (;;) { - let iHn = this.hostnameToSlotIdMap.get(hostname); - if ( iHn !== undefined ) { - do { - const strId = this.hostnameSlots[iHn+0]; - const str = this.strSlots[strId >>> this.nBits]; - if ( (strId & exceptionBit) !== 0 ) { - if ( str === value || str === '' ) { return false; } - } - if ( str === value ) { found = true; } - iHn = this.hostnameSlots[iHn+1]; - } while ( iHn !== 0 ); - } - if ( hostname === '' ) { break; } - const pos = hostname.indexOf('.'); - if ( pos !== -1 ) { - hostname = hostname.slice(pos + 1); - } else if ( hostname !== '*' ) { - hostname = '*'; - } else { - hostname = ''; - } - } - return found; - } - - toSelfie() { - return { - hostnameToSlotIdMap: Array.from(this.hostnameToSlotIdMap), - hostnameSlots: this.hostnameSlots, - strSlots: this.strSlots, - size: this.size - }; - } - - fromSelfie(selfie) { - if ( selfie === undefined ) { return; } - this.hostnameToSlotIdMap = new Map(selfie.hostnameToSlotIdMap); - this.hostnameSlots = selfie.hostnameSlots; - this.strSlots = selfie.strSlots; - this.size = selfie.size; - } -}; - -api.SessionDB = class { - constructor() { - this.db = new Map(); - } - compile(s) { - return s; - } - add(bits, s) { - const bucket = this.db.get(bits); - if ( bucket === undefined ) { - this.db.set(bits, new Set([ s ])); - } else { - bucket.add(s); - } - } - remove(bits, s) { - const bucket = this.db.get(bits); - if ( bucket === undefined ) { return; } - bucket.delete(s); - if ( bucket.size !== 0 ) { return; } - this.db.delete(bits); - } - retrieve(out) { - const mask = out.length - 1; - for ( const [ bits, bucket ] of this.db ) { - const i = bits & mask; - if ( out[i] instanceof Object === false ) { continue; } - for ( const s of bucket ) { - out[i].add(s); - } - } - } - has(bits, s) { - const selectors = this.db.get(bits); - return selectors !== undefined && selectors.has(s); - } - clear() { - this.db.clear(); - } - get isNotEmpty() { - return this.db.size !== 0; - } -}; - //-------------------------------------------------------------------------- // Public methods //-------------------------------------------------------------------------- -api.reset = function() { - µb.cosmeticFilteringEngine.reset(); - µb.scriptletFilteringEngine.reset(); - µb.httpheaderFilteringEngine.reset(); - µb.htmlFilteringEngine.reset(); +staticExtFilteringEngine.reset = function() { + cosmeticFilteringEngine.reset(); + scriptletFilteringEngine.reset(); + httpheaderFilteringEngine.reset(); + htmlFilteringEngine.reset(); }; -api.freeze = function() { - µb.cosmeticFilteringEngine.freeze(); - µb.scriptletFilteringEngine.freeze(); - µb.httpheaderFilteringEngine.freeze(); - µb.htmlFilteringEngine.freeze(); +staticExtFilteringEngine.freeze = function() { + cosmeticFilteringEngine.freeze(); + scriptletFilteringEngine.freeze(); + httpheaderFilteringEngine.freeze(); + htmlFilteringEngine.freeze(); }; -api.compile = function(parser, writer) { +staticExtFilteringEngine.compile = function(parser, writer) { if ( parser.category !== parser.CATStaticExtFilter ) { return false; } if ( (parser.flavorBits & parser.BITFlavorUnsupported) !== 0 ) { const who = writer.properties.get('name') || '?'; - µb.logger.writeOne({ + logger.writeOne({ realm: 'message', type: 'error', text: `Invalid extended filter in ${who}: ${parser.raw}` @@ -299,13 +109,13 @@ api.compile = function(parser, writer) { // Scriptlet injection if ( (parser.flavorBits & parser.BITFlavorExtScriptlet) !== 0 ) { - µb.scriptletFilteringEngine.compile(parser, writer); + scriptletFilteringEngine.compile(parser, writer); return true; } // Response header filtering if ( (parser.flavorBits & parser.BITFlavorExtResponseHeader) !== 0 ) { - µb.httpheaderFilteringEngine.compile(parser, writer); + httpheaderFilteringEngine.compile(parser, writer); return true; } @@ -313,67 +123,65 @@ api.compile = function(parser, writer) { // TODO: evaluate converting Adguard's `$$` syntax into uBO's HTML // filtering syntax. if ( (parser.flavorBits & parser.BITFlavorExtHTML) !== 0 ) { - µb.htmlFilteringEngine.compile(parser, writer); + htmlFilteringEngine.compile(parser, writer); return true; } // Cosmetic filtering - µb.cosmeticFilteringEngine.compile(parser, writer); + cosmeticFilteringEngine.compile(parser, writer); return true; }; -api.compileTemporary = function(parser) { +staticExtFilteringEngine.compileTemporary = function(parser) { if ( (parser.flavorBits & parser.BITFlavorExtScriptlet) !== 0 ) { - return µb.scriptletFilteringEngine.compileTemporary(parser); + return scriptletFilteringEngine.compileTemporary(parser); } if ( (parser.flavorBits & parser.BITFlavorExtResponseHeader) !== 0 ) { - return µb.httpheaderFilteringEngine.compileTemporary(parser); + return httpheaderFilteringEngine.compileTemporary(parser); } if ( (parser.flavorBits & parser.BITFlavorExtHTML) !== 0 ) { - return µb.htmlFilteringEngine.compileTemporary(parser); + return htmlFilteringEngine.compileTemporary(parser); } - return µb.cosmeticFilteringEngine.compileTemporary(parser); + return cosmeticFilteringEngine.compileTemporary(parser); }; -api.fromCompiledContent = function(reader, options) { - µb.cosmeticFilteringEngine.fromCompiledContent(reader, options); - µb.scriptletFilteringEngine.fromCompiledContent(reader, options); - µb.httpheaderFilteringEngine.fromCompiledContent(reader, options); - µb.htmlFilteringEngine.fromCompiledContent(reader, options); +staticExtFilteringEngine.fromCompiledContent = function(reader, options) { + cosmeticFilteringEngine.fromCompiledContent(reader, options); + scriptletFilteringEngine.fromCompiledContent(reader, options); + httpheaderFilteringEngine.fromCompiledContent(reader, options); + htmlFilteringEngine.fromCompiledContent(reader, options); }; -api.toSelfie = function(path) { - return µBlock.assets.put( +staticExtFilteringEngine.toSelfie = function(path) { + return io.put( `${path}/main`, JSON.stringify({ - cosmetic: µb.cosmeticFilteringEngine.toSelfie(), - scriptlets: µb.scriptletFilteringEngine.toSelfie(), - httpHeaders: µb.httpheaderFilteringEngine.toSelfie(), - html: µb.htmlFilteringEngine.toSelfie(), + cosmetic: cosmeticFilteringEngine.toSelfie(), + scriptlets: scriptletFilteringEngine.toSelfie(), + httpHeaders: httpheaderFilteringEngine.toSelfie(), + html: htmlFilteringEngine.toSelfie(), }) ); }; -api.fromSelfie = function(path) { - return µBlock.assets.get(`${path}/main`).then(details => { +staticExtFilteringEngine.fromSelfie = function(path) { + return io.get(`${path}/main`).then(details => { let selfie; try { selfie = JSON.parse(details.content); } catch (ex) { } if ( selfie instanceof Object === false ) { return false; } - µb.cosmeticFilteringEngine.fromSelfie(selfie.cosmetic); - µb.scriptletFilteringEngine.fromSelfie(selfie.scriptlets); - µb.httpheaderFilteringEngine.fromSelfie(selfie.httpHeaders); - µb.htmlFilteringEngine.fromSelfie(selfie.html); + cosmeticFilteringEngine.fromSelfie(selfie.cosmetic); + scriptletFilteringEngine.fromSelfie(selfie.scriptlets); + httpheaderFilteringEngine.fromSelfie(selfie.httpHeaders); + htmlFilteringEngine.fromSelfie(selfie.html); return true; }); }; /******************************************************************************/ -// Export - -µBlock.staticExtFilteringEngine = api; +export default staticExtFilteringEngine; /******************************************************************************/ diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index f71844ed6..0e539d057 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -3396,8 +3396,6 @@ FilterContainer.prototype.freeze = function() { const filterBucketId = FilterBucket.fid; const unserialize = CompiledListReader.unserialize; - const t0 = Date.now(); - for ( const line of this.goodFilters ) { if ( this.badFilters.has(line) ) { this.discardedCount += 1; @@ -3487,8 +3485,6 @@ FilterContainer.prototype.freeze = function() { this.optimize(); }, { timeout: 5000 }); } - - console.info(`staticNetFilteringEngine.freeze() took ${Date.now()-t0} ms`); }; /******************************************************************************/ @@ -3499,8 +3495,6 @@ FilterContainer.prototype.optimize = function() { this.optimizeTimerId = undefined; } - const t0 = Date.now(); - for ( let bits = 0, n = this.categories.length; bits < n; bits++ ) { const bucket = this.categories[bits]; if ( bucket === undefined ) { continue; } @@ -3523,8 +3517,6 @@ FilterContainer.prototype.optimize = function() { for ( let i = filterUnitWritePtr, n = filterUnits.length; i < n; i++ ) { filterUnits[i] = null; } - - console.info(`staticNetFilteringEngine.optimize() took ${Date.now()-t0} ms`); }; /******************************************************************************/ @@ -4477,9 +4469,7 @@ FilterContainer.prototype.benchmark = async function(requests, options = {}) { return text; } - const print = log.print; - - print(`Benchmarking staticNetFilteringEngine.matchRequest()...`); + console.info(`Benchmarking staticNetFilteringEngine.matchRequest()...`); const fctxt = new FilteringContext(); @@ -4489,12 +4479,12 @@ FilterContainer.prototype.benchmark = async function(requests, options = {}) { fctxt.setDocOriginFromURL(request.frameUrl); fctxt.setType(request.cpt); const r = this.matchRequest(fctxt); - print(`Result=${r}:`); - print(`\ttype=${fctxt.type}`); - print(`\turl=${fctxt.url}`); - print(`\tdocOrigin=${fctxt.getDocOrigin()}`); + console.info(`Result=${r}:`); + console.info(`\ttype=${fctxt.type}`); + console.info(`\turl=${fctxt.url}`); + console.info(`\tdocOrigin=${fctxt.getDocOrigin()}`); if ( r !== 0 ) { - console.log(this.toLogData()); + console.info(this.toLogData()); } return; } @@ -4524,11 +4514,11 @@ FilterContainer.prototype.benchmark = async function(requests, options = {}) { matchCount += 1; if ( recorded !== undefined ) { recorded.push(r); } if ( expected !== undefined && r !== expected[i] ) { - print(`Mismatch with reference results at ${i}:`); - print(`\tExpected ${expected[i]}, got ${r}:`); - print(`\ttype=${fctxt.type}`); - print(`\turl=${fctxt.url}`); - print(`\tdocOrigin=${fctxt.getDocOrigin()}`); + console.info(`Mismatch with reference results at ${i}:`); + console.info(`\tExpected ${expected[i]}, got ${r}:`); + console.info(`\ttype=${fctxt.type}`); + console.info(`\turl=${fctxt.url}`); + console.info(`\tdocOrigin=${fctxt.getDocOrigin()}`); } if ( r !== 1 ) { if ( this.hasQuery(fctxt) ) { @@ -4564,7 +4554,7 @@ FilterContainer.prototype.benchmark = async function(requests, options = {}) { ); } const s = output.join('\n'); - print(s); + console.info(s); return s; }; @@ -4576,9 +4566,9 @@ FilterContainer.prototype.test = async function(docURL, type, url) { fctxt.setType(type); fctxt.setURL(url); const r = this.matchRequest(fctxt); - console.log(`${r}`); + console.info(`${r}`); if ( r !== 0 ) { - console.log(this.toLogData()); + console.info(this.toLogData()); } }; @@ -4643,7 +4633,7 @@ FilterContainer.prototype.bucketHistogram = function() { results.sort((a, b) => { return b.size - a.size; }); - console.log(results); + console.info(results); }; /******************************************************************************* @@ -4733,7 +4723,7 @@ FilterContainer.prototype.filterClassHistogram = function() { const results = Array.from(filterClassDetails.values()).sort((a, b) => { return b.count - a.count; }); - console.log(results); + console.info(results); }; /******************************************************************************/ @@ -4772,14 +4762,12 @@ FilterContainer.prototype.tokenHistograms = async function(requests) { hitTokenMap.delete(token); } const tophits = Array.from(hitTokenMap).sort(customSort).slice(0, 100); - console.log('Misses:', JSON.stringify(topmisses)); - console.log('Hits:', JSON.stringify(tophits)); + console.info('Misses:', JSON.stringify(topmisses)); + console.info('Hits:', JSON.stringify(tophits)); }; /******************************************************************************/ const staticNetFilteringEngine = new FilterContainer(); -/******************************************************************************/ - -export { staticNetFilteringEngine }; +export default staticNetFilteringEngine; diff --git a/src/js/storage.js b/src/js/storage.js index 5ca65a30d..0bf1b392d 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -26,12 +26,24 @@ import '../lib/publicsuffixlist/publicsuffixlist.js'; import '../lib/punycode.js'; +import cosmeticFilteringEngine from './cosmetic-filtering.js'; import globals from './globals.js'; +import io from './assets.js'; +import logger from './logger.js'; +import lz4Codec from './lz4.js'; +import staticExtFilteringEngine from './static-ext-filtering.js'; +import staticFilteringReverseLookup from './reverselookup.js'; +import staticNetFilteringEngine from './static-net-filtering.js'; +import µb from './background.js'; import { hostnameFromURI } from './uri-utils.js'; -import { sparseBase64 } from './base64-custom.js'; import { LineIterator } from './text-iterators.js'; +import { permanentFirewall } from './dynamic-net-filtering.js'; +import { permanentSwitches } from './hnswitches.js'; +import { permanentURLFiltering } from './url-net-filtering.js'; +import { redirectEngine } from './redirect-engine.js'; +import { sparseBase64 } from './base64-custom.js'; import { StaticFilteringParser } from './static-filtering-parser.js'; -import µBlock from './background.js'; +import { ubolog, ubologSet } from './console.js'; import { CompiledListReader, @@ -40,7 +52,7 @@ import { /******************************************************************************/ -µBlock.getBytesInUse = async function() { +µb.getBytesInUse = async function() { const promises = []; let bytesInUse; @@ -71,17 +83,16 @@ import { if ( results.length > 1 && results[1] instanceof Object ) { processCount(results[1].usage); } - µBlock.storageUsed = bytesInUse; + µb.storageUsed = bytesInUse; return bytesInUse; }; /******************************************************************************/ -µBlock.saveLocalSettings = (( ) => { +µb.saveLocalSettings = (( ) => { const saveAfter = 4 * 60 * 1000; const onTimeout = ( ) => { - const µb = µBlock; if ( µb.localSettingsLastModified > µb.localSettingsLastSaved ) { µb.saveLocalSettings(); } @@ -98,7 +109,7 @@ import { /******************************************************************************/ -µBlock.loadUserSettings = async function() { +µb.loadUserSettings = async function() { const usDefault = this.userSettingsDefault; const results = await Promise.all([ @@ -126,7 +137,7 @@ import { return usUser; }; -µBlock.saveUserSettings = function() { +µb.saveUserSettings = function() { const toSave = this.getModifiedSettings( this.userSettings, this.userSettingsDefault @@ -154,7 +165,7 @@ import { // Admin hidden settings have precedence over user hidden settings. -µBlock.loadHiddenSettings = async function() { +µb.loadHiddenSettings = async function() { const hsDefault = this.hiddenSettingsDefault; const hsAdmin = this.hiddenSettingsAdmin; const hsUser = this.hiddenSettings; @@ -186,7 +197,7 @@ import { hsDefault[name] = hsAdmin[name] = hsUser[name] = value; } } - µBlock.noDashboard = disableDashboard === true; + µb.noDashboard = disableDashboard === true; if ( Array.isArray(disabledPopupPanelParts) ) { const partNameToBit = new Map([ [ 'globalStats', 0b00010 ], @@ -230,7 +241,7 @@ import { // This way the new default values in the future will properly apply for // those which were not modified by the user. -µBlock.saveHiddenSettings = function() { +µb.saveHiddenSettings = function() { vAPI.storage.set({ hiddenSettings: this.getModifiedSettings( this.hiddenSettings, @@ -240,8 +251,8 @@ import { }; self.addEventListener('hiddenSettingsChanged', ( ) => { - const µbhs = µBlock.hiddenSettings; - self.log.verbosity = µbhs.consoleLogLevel; + const µbhs = µb.hiddenSettings; + ubologSet(µbhs.consoleLogLevel === 'info'); vAPI.net.setOptions({ cnameIgnoreList: µbhs.cnameIgnoreList, cnameIgnore1stParty: µbhs.cnameIgnore1stParty, @@ -255,7 +266,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { /******************************************************************************/ -µBlock.hiddenSettingsFromString = function(raw) { +µb.hiddenSettingsFromString = function(raw) { const out = Object.assign({}, this.hiddenSettingsDefault); const lineIter = new LineIterator(raw); while ( lineIter.eot() === false ) { @@ -273,7 +284,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { return out; }; -µBlock.hiddenSettingValueFromString = function(name, value) { +µb.hiddenSettingValueFromString = function(name, value) { if ( typeof name !== 'string' || typeof value !== 'string' ) { return; } const hsDefault = this.hiddenSettingsDefault; if ( hsDefault.hasOwnProperty(name) === false ) { return; } @@ -305,7 +316,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { return r; }; -µBlock.stringFromHiddenSettings = function() { +µb.stringFromHiddenSettings = function() { const out = []; for ( const key of Object.keys(this.hiddenSettings).sort() ) { out.push(key + ' ' + this.hiddenSettings[key]); @@ -315,31 +326,31 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { /******************************************************************************/ -µBlock.savePermanentFirewallRules = function() { +µb.savePermanentFirewallRules = function() { vAPI.storage.set({ - dynamicFilteringString: this.permanentFirewall.toString() + dynamicFilteringString: permanentFirewall.toString() }); }; /******************************************************************************/ -µBlock.savePermanentURLFilteringRules = function() { +µb.savePermanentURLFilteringRules = function() { vAPI.storage.set({ - urlFilteringString: this.permanentURLFiltering.toString() + urlFilteringString: permanentURLFiltering.toString() }); }; /******************************************************************************/ -µBlock.saveHostnameSwitches = function() { +µb.saveHostnameSwitches = function() { vAPI.storage.set({ - hostnameSwitchesString: this.permanentSwitches.toString() + hostnameSwitchesString: permanentSwitches.toString() }); }; /******************************************************************************/ -µBlock.saveWhitelist = function() { +µb.saveWhitelist = function() { vAPI.storage.set({ netWhitelist: this.arrayFromWhitelist(this.netWhitelist) }); @@ -348,7 +359,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { /******************************************************************************/ -µBlock.loadSelectedFilterLists = async function() { +µb.loadSelectedFilterLists = async function() { const bin = await vAPI.storage.get('selectedFilterLists'); if ( bin instanceof Object && Array.isArray(bin.selectedFilterLists) ) { this.selectedFilterLists = bin.selectedFilterLists; @@ -357,11 +368,11 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { // https://github.com/gorhill/uBlock/issues/747 // Select default filter lists if first-time launch. - const lists = await this.assets.metadata(); + const lists = await io.metadata(); this.saveSelectedFilterLists(this.autoSelectRegionalFilterLists(lists)); }; -µBlock.saveSelectedFilterLists = function(newKeys, append = false) { +µb.saveSelectedFilterLists = function(newKeys, append = false) { const oldKeys = this.selectedFilterLists.slice(); if ( append ) { newKeys = newKeys.concat(oldKeys); @@ -380,7 +391,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { /******************************************************************************/ -µBlock.applyFilterListSelection = function(details) { +µb.applyFilterListSelection = function(details) { let selectedListKeySet = new Set(this.selectedFilterLists); let importedLists = this.userSettings.importedLists.slice(); @@ -458,7 +469,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { /******************************************************************************/ -µBlock.listKeysFromCustomFilterLists = function(raw) { +µb.listKeysFromCustomFilterLists = function(raw) { const urls = typeof raw === 'string' ? raw.trim().split(/[\n\r]+/) : raw; @@ -476,20 +487,20 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { /******************************************************************************/ -µBlock.saveUserFilters = function(content) { +µb.saveUserFilters = function(content) { // https://github.com/gorhill/uBlock/issues/1022 // Be sure to end with an empty line. content = content.trim(); if ( content !== '' ) { content += '\n'; } this.removeCompiledFilterList(this.userFiltersPath); - return this.assets.put(this.userFiltersPath, content); + return io.put(this.userFiltersPath, content); }; -µBlock.loadUserFilters = function() { - return this.assets.get(this.userFiltersPath); +µb.loadUserFilters = function() { + return io.get(this.userFiltersPath); }; -µBlock.appendUserFilters = async function(filters, options) { +µb.appendUserFilters = async function(filters, options) { filters = filters.trim(); if ( filters.length === 0 ) { return; } @@ -542,8 +553,8 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { const compiledFilters = this.compileFilters(filters, { assetKey: this.userFiltersPath }); - const snfe = this.staticNetFilteringEngine; - const cfe = this.cosmeticFilteringEngine; + const snfe = staticNetFilteringEngine; + const cfe = cosmeticFilteringEngine; const acceptedCount = snfe.acceptedCount + cfe.acceptedCount; const discardedCount = snfe.discardedCount + cfe.discardedCount; this.applyCompiledFilters(compiledFilters, true); @@ -557,9 +568,9 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { entry.entryCount += deltaEntryCount; entry.entryUsedCount += deltaEntryUsedCount; vAPI.storage.set({ 'availableFilterLists': this.availableFilterLists }); - this.staticNetFilteringEngine.freeze(); - this.redirectEngine.freeze(); - this.staticExtFilteringEngine.freeze(); + staticNetFilteringEngine.freeze(); + redirectEngine.freeze(); + staticExtFilteringEngine.freeze(); this.selfieManager.destroy(); // https://www.reddit.com/r/uBlockOrigin/comments/cj7g7m/ @@ -571,18 +582,18 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { vAPI.messaging.broadcast({ what: 'userFiltersUpdated' }); }; -µBlock.createUserFilters = function(details) { +µb.createUserFilters = function(details) { this.appendUserFilters(details.filters, details); // https://github.com/gorhill/uBlock/issues/1786 if ( details.docURL === undefined ) { return; } - this.cosmeticFilteringEngine.removeFromSelectorCache( + cosmeticFilteringEngine.removeFromSelectorCache( hostnameFromURI(details.docURL) ); }; /******************************************************************************/ -µBlock.autoSelectRegionalFilterLists = function(lists) { +µb.autoSelectRegionalFilterLists = function(lists) { const selectedListKeys = [ this.userFiltersPath ]; for ( const key in lists ) { if ( lists.hasOwnProperty(key) === false ) { continue; } @@ -601,7 +612,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { /******************************************************************************/ -µBlock.getAvailableLists = async function() { +µb.getAvailableLists = async function() { let oldAvailableLists = {}, newAvailableLists = {}; @@ -625,7 +636,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { title: '', }; newAvailableLists[listKey] = entry; - this.assets.registerAssetSource(listKey, entry); + io.registerAssetSource(listKey, entry); } // Convert a no longer existing stock list into an imported list. @@ -645,7 +656,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { title: oldEntry.title || '' }; newAvailableLists[listURL] = newEntry; - this.assets.registerAssetSource(listURL, newEntry); + io.registerAssetSource(listURL, newEntry); importedListKeys.push(listURL); this.userSettings.importedLists.push(listURL.trim()); this.saveUserSettings(); @@ -654,8 +665,8 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { const promises = [ vAPI.storage.get('availableFilterLists'), - this.assets.metadata(), - this.badLists.size === 0 ? this.assets.get('ublock-badlists') : false, + io.metadata(), + this.badLists.size === 0 ? io.get('ublock-badlists') : false, ]; // Load previously saved available lists -- these contains data @@ -731,7 +742,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { if ( newEntry.submitter !== 'user' ) { continue; } if ( importedListKeys.indexOf(assetKey) !== -1 ) { continue; } delete newAvailableLists[assetKey]; - this.assets.unregisterAssetSource(assetKey); + io.unregisterAssetSource(assetKey); this.removeFilterList(assetKey); } @@ -740,17 +751,17 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { /******************************************************************************/ -µBlock.loadFilterLists = (( ) => { +µb.loadFilterLists = (( ) => { const loadedListKeys = []; let loadingPromise; let t0 = 0; const onDone = function() { - log.info(`loadFilterLists() took ${Date.now()-t0} ms`); + ubolog(`loadFilterLists() took ${Date.now()-t0} ms`); - this.staticNetFilteringEngine.freeze(); - this.staticExtFilteringEngine.freeze(); - this.redirectEngine.freeze(); + staticNetFilteringEngine.freeze(); + staticExtFilteringEngine.freeze(); + redirectEngine.freeze(); vAPI.net.unsuspend(); vAPI.storage.set({ 'availableFilterLists': this.availableFilterLists }); @@ -763,15 +774,15 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { }); this.selfieManager.destroy(); - this.lz4Codec.relinquish(); + lz4Codec.relinquish(); this.compiledFormatChanged = false; loadingPromise = undefined; }; const applyCompiledFilters = function(assetKey, compiled) { - const snfe = this.staticNetFilteringEngine; - const sxfe = this.staticExtFilteringEngine; + const snfe = staticNetFilteringEngine; + const sxfe = staticExtFilteringEngine; let acceptedCount = snfe.acceptedCount + sxfe.acceptedCount, discardedCount = snfe.discardedCount + sxfe.discardedCount; this.applyCompiledFilters(compiled, assetKey === this.userFiltersPath); @@ -789,15 +800,15 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { this.availableFilterLists = lists; vAPI.net.suspend(); - this.redirectEngine.reset(); - this.staticExtFilteringEngine.reset(); - this.staticNetFilteringEngine.reset(); + redirectEngine.reset(); + staticExtFilteringEngine.reset(); + staticNetFilteringEngine.reset(); this.selfieManager.destroy(); - this.staticFilteringReverseLookup.resetLists(); + staticFilteringReverseLookup.resetLists(); // We need to build a complete list of assets to pull first: this is // because it *may* happens that some load operations are synchronous: - // This happens for assets which do not exist, ot assets with no + // This happens for assets which do not exist, or assets with no // content. const toLoad = []; for ( const assetKey in lists ) { @@ -837,7 +848,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { /******************************************************************************/ -µBlock.getCompiledFilterList = async function(assetKey) { +µb.getCompiledFilterList = async function(assetKey) { const compiledPath = 'compiled/' + assetKey; // https://github.com/uBlockOrigin/uBlock-issues/issues/1365 @@ -847,7 +858,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { this.compiledFormatChanged === false && this.badLists.has(assetKey) === false ) { - const compiledDetails = await this.assets.get(compiledPath); + const compiledDetails = await io.get(compiledPath); if ( parseInt(compiledDetails.content, 10) === this.systemSettings.compiledMagic @@ -862,7 +873,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { return { assetKey, content: '' }; } - const rawDetails = await this.assets.get(assetKey, { silent: true }); + const rawDetails = await io.get(assetKey, { silent: true }); // Compiling an empty string results in an empty string. if ( rawDetails.content === '' ) { rawDetails.assetKey = assetKey; @@ -878,7 +889,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { const compiledContent = this.compileFilters(rawDetails.content, { assetKey }); - this.assets.put(compiledPath, compiledContent); + io.put(compiledPath, compiledContent); return { assetKey, content: compiledContent }; }; @@ -892,7 +903,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { // the whole raw filter list to be held in memory just because we cut out // the title as a substring. -µBlock.extractFilterListMetadata = function(assetKey, raw) { +µb.extractFilterListMetadata = function(assetKey, raw) { const listEntry = this.availableFilterLists[assetKey]; if ( listEntry === undefined ) { return; } // Metadata expected to be found at the top of content. @@ -904,13 +915,13 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { const title = matches && matches[1].trim() || ''; if ( title !== '' && title !== listEntry.title ) { listEntry.title = this.orphanizeString(title); - this.assets.registerAssetSource(assetKey, { title }); + io.registerAssetSource(assetKey, { title }); } matches = head.match(/(?:^|\n)(?:!|# )[\t ]*Homepage[\t ]*:[\t ]*(https?:\/\/\S+)\s/i); const supportURL = matches && matches[1] || ''; if ( supportURL !== '' && supportURL !== listEntry.supportURL ) { listEntry.supportURL = this.orphanizeString(supportURL); - this.assets.registerAssetSource(assetKey, { supportURL }); + io.registerAssetSource(assetKey, { supportURL }); } } // Extract update frequency information @@ -924,7 +935,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { updateAfter = Math.max(updateAfter, 1); if ( updateAfter !== listEntry.updateAfter ) { listEntry.updateAfter = updateAfter; - this.assets.registerAssetSource(assetKey, { updateAfter }); + io.registerAssetSource(assetKey, { updateAfter }); } } } @@ -932,18 +943,18 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { /******************************************************************************/ -µBlock.removeCompiledFilterList = function(assetKey) { - this.assets.remove('compiled/' + assetKey); +µb.removeCompiledFilterList = function(assetKey) { + io.remove('compiled/' + assetKey); }; -µBlock.removeFilterList = function(assetKey) { +µb.removeFilterList = function(assetKey) { this.removeCompiledFilterList(assetKey); - this.assets.remove(assetKey); + io.remove(assetKey); }; /******************************************************************************/ -µBlock.compileFilters = function(rawText, details = {}) { +µb.compileFilters = function(rawText, details = {}) { const writer = new CompiledListWriter(); // Populate the writer with information potentially useful to the @@ -957,8 +968,6 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { // Useful references: // https://adblockplus.org/en/filter-cheatsheet // https://adblockplus.org/en/filters - const staticNetFilteringEngine = this.staticNetFilteringEngine; - const staticExtFilteringEngine = this.staticExtFilteringEngine; const lineIter = new LineIterator(this.preparseDirectives.prune(rawText)); const parser = new StaticFilteringParser({ expertMode }); @@ -990,7 +999,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { } if ( staticNetFilteringEngine.compile(parser, writer) ) { continue; } if ( staticNetFilteringEngine.error !== undefined ) { - this.logger.writeOne({ + logger.writeOne({ realm: 'message', type: 'error', text: staticNetFilteringEngine.error @@ -1013,11 +1022,11 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { // Added `firstparty` argument: to avoid discarding cosmetic filters when // applying 1st-party filters. -µBlock.applyCompiledFilters = function(rawText, firstparty) { +µb.applyCompiledFilters = function(rawText, firstparty) { if ( rawText === '' ) { return; } const reader = new CompiledListReader(rawText); - this.staticNetFilteringEngine.fromCompiled(reader); - this.staticExtFilteringEngine.fromCompiledContent(reader, { + staticNetFilteringEngine.fromCompiled(reader); + staticExtFilteringEngine.fromCompiledContent(reader, { skipGenericCosmetic: this.userSettings.ignoreGenericCosmeticFilters, skipCosmetic: !firstparty && !this.userSettings.parseAllABPHideFilters }); @@ -1027,7 +1036,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { // https://github.com/AdguardTeam/AdguardBrowserExtension/issues/917 -µBlock.preparseDirectives = { +µb.preparseDirectives = { // This method returns an array of indices, corresponding to position in // the content string which should alternatively be parsed and discarded. split: function(content) { @@ -1140,19 +1149,19 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { /******************************************************************************/ -µBlock.loadRedirectResources = async function() { +µb.loadRedirectResources = async function() { try { - const success = await this.redirectEngine.resourcesFromSelfie(); + const success = await redirectEngine.resourcesFromSelfie(); if ( success === true ) { return true; } const fetchPromises = [ - this.redirectEngine.loadBuiltinResources() + redirectEngine.loadBuiltinResources() ]; const userResourcesLocation = this.hiddenSettings.userResourcesLocation; if ( userResourcesLocation !== 'unset' ) { for ( const url of userResourcesLocation.split(/\s+/) ) { - fetchPromises.push(this.assets.fetchText(url)); + fetchPromises.push(io.fetchText(url)); } } @@ -1172,10 +1181,10 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { content += '\n\n' + result.content; } - this.redirectEngine.resourcesFromString(content); - this.redirectEngine.selfieFromResources(); + redirectEngine.resourcesFromString(content); + redirectEngine.selfieFromResources(); } catch(ex) { - log.info(ex); + ubolog(ex); return false; } return true; @@ -1183,31 +1192,31 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { /******************************************************************************/ -µBlock.loadPublicSuffixList = async function() { +µb.loadPublicSuffixList = async function() { const psl = globals.publicSuffixList; if ( this.hiddenSettings.disableWebAssembly !== true ) { psl.enableWASM('/lib/publicsuffixlist'); } try { - const result = await this.assets.get(`compiled/${this.pslAssetKey}`); + const result = await io.get(`compiled/${this.pslAssetKey}`); if ( psl.fromSelfie(result.content, sparseBase64) ) { return; } } catch (ex) { - log.info(ex); + ubolog(ex); } - const result = await this.assets.get(this.pslAssetKey); + const result = await io.get(this.pslAssetKey); if ( result.content !== '' ) { this.compilePublicSuffixList(result.content); } }; -µBlock.compilePublicSuffixList = function(content) { +µb.compilePublicSuffixList = function(content) { const psl = globals.publicSuffixList; psl.parse(content, globals.punycode.toASCII); - this.assets.put(`compiled/${this.pslAssetKey}`, psl.toSelfie(sparseBase64)); + io.put(`compiled/${this.pslAssetKey}`, psl.toSelfie(sparseBase64)); }; /******************************************************************************/ @@ -1216,8 +1225,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { // be generated if the user doesn't change his filter lists selection for // some set time. -µBlock.selfieManager = (( ) => { - const µb = µBlock; +µb.selfieManager = (( ) => { let createTimer; let destroyTimer; @@ -1227,28 +1235,27 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { const create = async function() { await Promise.all([ - µb.assets.put( + io.put( 'selfie/main', JSON.stringify({ magic: µb.systemSettings.selfieMagic, availableFilterLists: µb.availableFilterLists, }) ), - µb.redirectEngine.toSelfie('selfie/redirectEngine'), - µb.staticExtFilteringEngine.toSelfie( + redirectEngine.toSelfie('selfie/redirectEngine'), + staticExtFilteringEngine.toSelfie( 'selfie/staticExtFilteringEngine' ), - µb.staticNetFilteringEngine.toSelfie( - µb.assets, + staticNetFilteringEngine.toSelfie(io, 'selfie/staticNetFilteringEngine' ), ]); - µb.lz4Codec.relinquish(); + lz4Codec.relinquish(); µb.selfieIsInvalid = false; }; const loadMain = async function() { - const details = await µb.assets.get('selfie/main'); + const details = await io.get('selfie/main'); if ( details instanceof Object === false || typeof details.content !== 'string' || @@ -1278,12 +1285,11 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { try { const results = await Promise.all([ loadMain(), - µb.redirectEngine.fromSelfie('selfie/redirectEngine'), - µb.staticExtFilteringEngine.fromSelfie( + redirectEngine.fromSelfie('selfie/redirectEngine'), + staticExtFilteringEngine.fromSelfie( 'selfie/staticExtFilteringEngine' ), - µb.staticNetFilteringEngine.fromSelfie( - µb.assets, + staticNetFilteringEngine.fromSelfie(io, 'selfie/staticNetFilteringEngine' ), ]); @@ -1292,14 +1298,14 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { } } catch (reason) { - log.info(reason); + ubolog(reason); } destroy(); return false; }; const destroy = function() { - µb.assets.remove(/^selfie\//); + io.remove(/^selfie\//); µb.selfieIsInvalid = true; createTimer = vAPI.setTimeout(( ) => { createTimer = undefined; @@ -1335,7 +1341,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { // necessarily present, i.e. administrators may removed entries which // values are left to the user's choice. -µBlock.restoreAdminSettings = async function() { +µb.restoreAdminSettings = async function() { let toOverwrite = {}; let data; try { @@ -1368,7 +1374,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { typeof data.assetsBootstrapLocation === 'string' && data.assetsBootstrapLocation !== '' ) { - µBlock.assetsBootstrapLocation = data.assetsBootstrapLocation; + µb.assetsBootstrapLocation = data.assetsBootstrapLocation; } if ( typeof data.userSettings === 'object' ) { @@ -1409,7 +1415,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { Array.isArray(toOverwrite.trustedSiteDirectives) && toOverwrite.trustedSiteDirectives.length !== 0 ) { - µBlock.netWhitelistDefault = toOverwrite.trustedSiteDirectives.slice(); + µb.netWhitelistDefault = toOverwrite.trustedSiteDirectives.slice(); bin.netWhitelist = toOverwrite.trustedSiteDirectives.slice(); binNotEmpty = true; } else if ( Array.isArray(data.whitelist) ) { @@ -1457,7 +1463,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { // https://github.com/gorhill/uBlock/issues/3210 // Support ability to auto-enable a filter list based on user agent. -µBlock.listMatchesEnvironment = function(details) { +µb.listMatchesEnvironment = function(details) { // Matches language? if ( typeof details.lang === 'string' ) { let re = this.listMatchesEnvironment.reLang; @@ -1480,7 +1486,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { /******************************************************************************/ -µBlock.scheduleAssetUpdater = (( ) => { +µb.scheduleAssetUpdater = (( ) => { let timer, next = 0; return function(updateDelay) { @@ -1502,7 +1508,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { timer = vAPI.setTimeout(( ) => { timer = undefined; next = 0; - this.assets.updateStart({ + io.updateStart({ delay: this.hiddenSettings.autoUpdateAssetFetchPeriod * 1000 || 120000, auto: true, @@ -1513,7 +1519,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { /******************************************************************************/ -µBlock.assetObserver = function(topic, details) { +µb.assetObserver = function(topic, details) { // Do not update filter list if not in use. // Also, ignore really bad lists, i.e. those which should not even be // fetched from a remote server. @@ -1544,7 +1550,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { details.content ); if ( this.badLists.has(details.assetKey) === false ) { - this.assets.put( + io.put( 'compiled/' + details.assetKey, this.compileFilters(details.content, { assetKey: details.assetKey @@ -1592,7 +1598,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { this.hiddenSettings.userResourcesLocation !== 'unset' || vAPI.webextFlavor.soup.has('devbuild') ) { - this.redirectEngine.invalidateResourcesSelfie(); + redirectEngine.invalidateResourcesSelfie(); } this.loadFilterLists(); } diff --git a/src/js/tab.js b/src/js/tab.js index 046df4ddf..45ee4ab02 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -23,6 +23,16 @@ /******************************************************************************/ +import contextMenu from './contextmenu.js'; +import logger from './logger.js'; +import scriptletFilteringEngine from './scriptlet-filtering.js'; +import staticNetFilteringEngine from './static-net-filtering.js'; +import µb from './background.js'; +import { PageStore } from './pagestore.js'; +import { sessionFirewall } from './dynamic-net-filtering.js'; +import { sessionSwitches } from './hnswitches.js'; +import { sessionURLFiltering } from './url-net-filtering.js'; + import { domainFromHostname, hostnameFromURI, @@ -30,8 +40,6 @@ import { originFromURI, } from './uri-utils.js'; -import µBlock from './background.js'; - /******************************************************************************/ /******************************************************************************/ @@ -44,7 +52,7 @@ import µBlock from './background.js'; // hostname. This way, for a specific scheme you can create scope with // rules which will apply only to that scheme. -µBlock.normalizeTabURL = (( ) => { +µb.normalizeTabURL = (( ) => { const tabURLNormalizer = new URL('about:blank'); return (tabId, tabURL) => { @@ -93,9 +101,7 @@ import µBlock from './background.js'; // c: close opener // d: close target -µBlock.onPopupUpdated = (( ) => { - const µb = µBlock; - +const onPopupUpdated = (( ) => { // https://github.com/gorhill/uBlock/commit/1d448b85b2931412508aa01bf899e0b6f0033626#commitcomment-14944764 // See if two URLs are different, disregarding scheme -- because the // scheme can be unilaterally changed by the browser. @@ -162,13 +168,13 @@ import µBlock from './background.js'; // popunders. if ( popupType === 'popup' && - µb.sessionSwitches.evaluateZ( + sessionSwitches.evaluateZ( 'no-popups', fctxt.getTabHostname() ) ) { fctxt.filter = { - raw: 'no-popups: ' + µb.sessionSwitches.z + ' true', + raw: 'no-popups: ' + sessionSwitches.z + ' true', result: 1, source: 'switch' }; @@ -178,16 +184,16 @@ import µBlock from './background.js'; // https://github.com/gorhill/uBlock/issues/581 // Take into account popup-specific rules in dynamic URL // filtering, OR generic allow rules. - let result = µb.sessionURLFiltering.evaluateZ( + let result = sessionURLFiltering.evaluateZ( fctxt.getTabHostname(), targetURL, popupType ); if ( - result === 1 && µb.sessionURLFiltering.type === popupType || + result === 1 && sessionURLFiltering.type === popupType || result === 2 ) { - fctxt.filter = µb.sessionURLFiltering.toLogData(); + fctxt.filter = sessionURLFiltering.toLogData(); return result; } @@ -195,21 +201,21 @@ import µBlock from './background.js'; // Take into account `allow` rules in dynamic filtering: `block` // rules are ignored, as block rules are not meant to block // specific types like `popup` (just like with static filters). - result = µb.sessionFirewall.evaluateCellZY( + result = sessionFirewall.evaluateCellZY( fctxt.getTabHostname(), fctxt.getHostname(), popupType ); if ( result === 2 ) { - fctxt.filter = µb.sessionFirewall.toLogData(); + fctxt.filter = sessionFirewall.toLogData(); return 2; } } fctxt.type = popupType; - const result = µb.staticNetFilteringEngine.matchRequest(fctxt, 0b0001); + const result = staticNetFilteringEngine.matchRequest(fctxt, 0b0001); if ( result !== 0 ) { - fctxt.filter = µb.staticNetFilteringEngine.toLogData(); + fctxt.filter = staticNetFilteringEngine.toLogData(); return result; } @@ -225,11 +231,11 @@ import µBlock from './background.js'; if ( fctxt.filter === undefined || fctxt.filter !== 'static' || - fctxt.filter.token === µb.staticNetFilteringEngine.noTokenHash + fctxt.filter.token === staticNetFilteringEngine.noTokenHash ) { return 0; } - if ( fctxt.filter.token === µb.staticNetFilteringEngine.dotTokenHash ) { + if ( fctxt.filter.token === staticNetFilteringEngine.dotTokenHash ) { return result; } const re = new RegExp(fctxt.filter.regex, 'i'); @@ -357,7 +363,7 @@ import µBlock from './background.js'; // Log only for when there was a hit against an actual filter (allow or block). // https://github.com/gorhill/uBlock/issues/2776 - if ( µb.logger.enabled ) { + if ( logger.enabled ) { fctxt.setRealm('network').setType(popupType); if ( popupType === 'popup' ) { fctxt.setURL(targetURL) @@ -463,8 +469,7 @@ housekeep itself. */ -µBlock.tabContextManager = (( ) => { - const µb = µBlock; +µb.tabContextManager = (( ) => { const tabContexts = new Map(); // https://github.com/chrisaljoudi/uBlock/issues/1001 @@ -524,7 +529,7 @@ housekeep itself. if ( targetTabId === candidate.opener.tabId ) { candidate.opener.popunder = true; } - const result = await µb.onPopupUpdated(tabId, candidate.opener); + const result = onPopupUpdated(tabId, candidate.opener); if ( result === true ) { candidate.destroy(); } else { @@ -864,20 +869,20 @@ vAPI.Tabs = class extends vAPI.Tabs { super.onActivated(details); if ( vAPI.isBehindTheSceneTabId(details.tabId) ) { return; } // https://github.com/uBlockOrigin/uBlock-issues/issues/680 - µBlock.updateToolbarIcon(details.tabId); - µBlock.contextMenu.update(details.tabId); + µb.updateToolbarIcon(details.tabId); + contextMenu.update(details.tabId); } onClosed(tabId) { super.onClosed(tabId); if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } - µBlock.unbindTabFromPageStore(tabId); - µBlock.contextMenu.update(); + µb.unbindTabFromPageStore(tabId); + contextMenu.update(); } onCreated(details) { super.onCreated(details); - µBlock.tabContextManager.onTabCreated(details); + µb.tabContextManager.onTabCreated(details); } // When the DOM content of root frame is loaded, this means the tab @@ -895,7 +900,6 @@ vAPI.Tabs = class extends vAPI.Tabs { // is not available at this point. onNavigation(details) { super.onNavigation(details); - const µb = µBlock; const { frameId, tabId, url } = details; if ( frameId === 0 ) { µb.tabContextManager.commit(tabId, url); @@ -912,7 +916,7 @@ vAPI.Tabs = class extends vAPI.Tabs { isNetworkURI(url) && pageStore.getNetFilteringSwitch() ) { - µb.scriptletFilteringEngine.injectNow(details); + scriptletFilteringEngine.injectNow(details); } } @@ -924,8 +928,8 @@ vAPI.Tabs = class extends vAPI.Tabs { super.onUpdated(tabId, changeInfo, tab); if ( !tab.url || tab.url === '' ) { return; } if ( !changeInfo.url ) { return; } - µBlock.tabContextManager.commit(tabId, changeInfo.url); - µBlock.bindTabToPageStore(tabId, 'tabUpdated', tab); + µb.tabContextManager.commit(tabId, changeInfo.url); + µb.bindTabToPageStore(tabId, 'tabUpdated', tab); } }; @@ -936,7 +940,7 @@ vAPI.tabs = new vAPI.Tabs(); // Create an entry for the tab if it doesn't exist. -µBlock.bindTabToPageStore = function(tabId, context, details = undefined) { +µb.bindTabToPageStore = function(tabId, context, details = undefined) { this.updateToolbarIcon(tabId, 0b111); // Do not create a page store for URLs which are of no interests @@ -950,7 +954,7 @@ vAPI.tabs = new vAPI.Tabs(); // Tab is not bound if ( pageStore === undefined ) { - pageStore = this.PageStore.factory(tabId, details); + pageStore = PageStore.factory(tabId, details); this.pageStores.set(tabId, pageStore); this.pageStoresToken = Date.now(); return pageStore; @@ -981,7 +985,7 @@ vAPI.tabs = new vAPI.Tabs(); /******************************************************************************/ -µBlock.unbindTabFromPageStore = function(tabId) { +µb.unbindTabFromPageStore = function(tabId) { const pageStore = this.pageStores.get(tabId); if ( pageStore === undefined ) { return; } pageStore.dispose(); @@ -991,11 +995,11 @@ vAPI.tabs = new vAPI.Tabs(); /******************************************************************************/ -µBlock.pageStoreFromTabId = function(tabId) { +µb.pageStoreFromTabId = function(tabId) { return this.pageStores.get(tabId) || null; }; -µBlock.mustPageStoreFromTabId = function(tabId) { +µb.mustPageStoreFromTabId = function(tabId) { return this.pageStores.get(tabId) || this.pageStores.get(vAPI.noTabId); }; @@ -1008,19 +1012,19 @@ vAPI.tabs = new vAPI.Tabs(); // the document context (if present) of the network request. { - const NoPageStore = class extends µBlock.PageStore { + const NoPageStore = class extends PageStore { getNetFilteringSwitch(fctxt) { if ( fctxt ) { const docOrigin = fctxt.getDocOrigin(); if ( docOrigin ) { - return µBlock.getNetFilteringSwitch(docOrigin); + return µb.getNetFilteringSwitch(docOrigin); } } return super.getNetFilteringSwitch(); } }; const pageStore = new NoPageStore(vAPI.noTabId); - µBlock.pageStores.set(pageStore.tabId, pageStore); + µb.pageStores.set(pageStore.tabId, pageStore); pageStore.title = vAPI.i18n('logBehindTheScene'); } @@ -1028,8 +1032,7 @@ vAPI.tabs = new vAPI.Tabs(); // Update visual of extension icon. -µBlock.updateToolbarIcon = (( ) => { - const µb = µBlock; +µb.updateToolbarIcon = (( ) => { const tabIdToDetails = new Map(); const computeBadgeColor = (bits) => { @@ -1118,11 +1121,11 @@ vAPI.tabs = new vAPI.Tabs(); const checkTab = async tabId => { const tab = await vAPI.tabs.get(tabId); if ( tab instanceof Object && tab.discarded !== true ) { return; } - µBlock.unbindTabFromPageStore(tabId); + µb.unbindTabFromPageStore(tabId); }; const pageStoreJanitor = function() { - const tabIds = Array.from(µBlock.pageStores.keys()).sort(); + const tabIds = Array.from(µb.pageStores.keys()).sort(); if ( pageStoreJanitorSampleAt >= tabIds.length ) { pageStoreJanitorSampleAt = 0; } diff --git a/src/js/text-encode.js b/src/js/text-encode.js index 3eb6dc166..302df2cc4 100644 --- a/src/js/text-encode.js +++ b/src/js/text-encode.js @@ -23,17 +23,17 @@ /******************************************************************************/ -import µBlock from './background.js'; +import µb from './background.js'; /******************************************************************************/ -µBlock.textEncode = (function() { +const textEncode = (( ) => { - if ( µBlock.canFilterResponseData !== true ) { return; } + if ( µb.canFilterResponseData !== true ) { return; } // charset aliases extracted from: // https://github.com/inexorabletash/text-encoding/blob/b4e5bc26e26e51f56e3daa9f13138c79f49d3c34/lib/encoding.js#L342 - var normalizedCharset = new Map([ + const normalizedCharset = new Map([ [ 'utf8', 'utf-8' ], [ 'unicode-1-1-utf-8', 'utf-8' ], [ 'utf-8', 'utf-8' ], @@ -66,7 +66,7 @@ import µBlock from './background.js'; ]); // http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1250.TXT - var cp1250_range0 = new Uint8Array([ + const cp1250_range0 = new Uint8Array([ /* 0x0100 */ 0x00, 0x00, 0xC3, 0xE3, 0xA5, 0xB9, 0xC6, 0xE6, /* 0x0108 */ 0x00, 0x00, 0x00, 0x00, 0xC8, 0xE8, 0xCF, 0xEF, /* 0x0110 */ 0xD0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -86,7 +86,7 @@ import µBlock from './background.js'; ]); // http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1251.TXT - var cp1251_range0 = new Uint8Array([ + const cp1251_range0 = new Uint8Array([ /* 0x0400 */ 0x00, 0xA8, 0x80, 0x81, 0xAA, 0xBD, 0xB2, 0xAF, /* 0x0408 */ 0xA3, 0x8A, 0x8C, 0x8E, 0x8D, 0x00, 0xA1, 0x8F, /* 0x0410 */ 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, @@ -109,7 +109,7 @@ import µBlock from './background.js'; ]); // https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT - var cp1252_range0 = new Uint8Array([ + const cp1252_range0 = new Uint8Array([ /* 0x0150 */ 0x00, 0x00, 0x8C, 0x9C, 0x00, 0x00, 0x00, 0x00, /* 0x0158 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x0160 */ 0x8A, 0x9A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -118,7 +118,7 @@ import µBlock from './background.js'; /* 0x0178 */ 0x9F, 0x00, 0x00, 0x00, 0x00, 0x8E, 0x9E, 0x00 ]); - var cp125x_range0 = new Uint8Array([ + const cp125x_range0 = new Uint8Array([ /* 0x2010 */ 0x00, 0x00, 0x00, 0x96, 0x97, 0x00, 0x00, 0x00, /* 0x2018 */ 0x91, 0x92, 0x82, 0x00, 0x93, 0x94, 0x84, 0x00, /* 0x2020 */ 0x86, 0x87, 0x95, 0x00, 0x00, 0x00, 0x85, 0x00, @@ -127,9 +127,9 @@ import µBlock from './background.js'; /* 0x2038 */ 0x00, 0x8B, 0x9B, 0x00, 0x00, 0x00, 0x00, 0x00 ]); - var encoders = { + const encoders = { 'windows-1250': function(buf) { - var i = 0, n = buf.byteLength, o = 0, c; + let i = 0, n = buf.byteLength, o = 0, c; while ( i < n ) { c = buf[i++]; if ( c < 0x80 ) { @@ -174,7 +174,7 @@ import µBlock from './background.js'; return buf.slice(0, o); }, 'windows-1251': function(buf) { - var i = 0, n = buf.byteLength, o = 0, c; + let i = 0, n = buf.byteLength, o = 0, c; while ( i < n ) { c = buf[i++]; if ( c < 0x80 ) { @@ -211,7 +211,7 @@ import µBlock from './background.js'; return buf.slice(0, o); }, 'windows-1252': function(buf) { - var i = 0, n = buf.byteLength, o = 0, c; + let i = 0, n = buf.byteLength, o = 0, c; while ( i < n ) { c = buf[i++]; if ( c < 0x80 ) { @@ -267,3 +267,9 @@ import µBlock from './background.js'; } }; })(); + +/******************************************************************************/ + +export default textEncode; + +/******************************************************************************/ diff --git a/src/js/traffic.js b/src/js/traffic.js index d76805ca3..7dbec5677 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -23,13 +23,22 @@ /******************************************************************************/ +import htmlFilteringEngine from './html-filtering.js'; +import httpheaderFilteringEngine from './httpheader-filtering.js'; +import logger from './logger.js'; +import scriptletFilteringEngine from './scriptlet-filtering.js'; +import staticNetFilteringEngine from './static-net-filtering.js'; +import textEncode from './text-encode.js'; +import µb from './background.js'; +import { sessionFirewall } from './dynamic-net-filtering.js'; +import { sessionSwitches } from './hnswitches.js'; +import { sessionURLFiltering } from './url-net-filtering.js'; + import { entityFromDomain, isNetworkURI, } from './uri-utils.js'; -import µBlock from './background.js'; - /******************************************************************************/ // Platform-specific behavior. @@ -55,7 +64,7 @@ const supportsFloc = document.interestCohort instanceof Function; // Intercept and filter web requests. const onBeforeRequest = function(details) { - const fctxt = µBlock.filteringContext.fromWebrequestDetails(details); + const fctxt = µb.filteringContext.fromWebrequestDetails(details); // Special handling for root document. // https://github.com/chrisaljoudi/uBlock/issues/1001 @@ -72,7 +81,6 @@ const onBeforeRequest = function(details) { } // Lookup the page store associated with this tab id. - const µb = µBlock; let pageStore = µb.pageStoreFromTabId(tabId); if ( pageStore === null ) { const tabContext = µb.tabContextManager.mustLookup(tabId); @@ -87,7 +95,7 @@ const onBeforeRequest = function(details) { pageStore.journalAddRequest(fctxt, result); - if ( µb.logger.enabled ) { + if ( logger.enabled ) { fctxt.setRealm('network').toLogger(); } @@ -121,7 +129,6 @@ const onBeforeRequest = function(details) { /******************************************************************************/ const onBeforeRootFrameRequest = function(fctxt) { - const µb = µBlock; const requestURL = fctxt.url; // Special handling for root document. @@ -129,7 +136,6 @@ const onBeforeRootFrameRequest = function(fctxt) { // This must be executed regardless of whether the request is // behind-the-scene const requestHostname = fctxt.getHostname(); - const loggerEnabled = µb.logger.enabled; let result = 0; let logData; @@ -137,7 +143,7 @@ const onBeforeRootFrameRequest = function(fctxt) { const trusted = µb.getNetFilteringSwitch(requestURL) === false; if ( trusted ) { result = 2; - if ( loggerEnabled ) { + if ( logger.enabled ) { logData = { engine: 'u', result: 2, raw: 'whitelisted' }; } } @@ -145,14 +151,14 @@ const onBeforeRootFrameRequest = function(fctxt) { // Permanently unrestricted? if ( result === 0 && - µb.sessionSwitches.evaluateZ('no-strict-blocking', requestHostname) + sessionSwitches.evaluateZ('no-strict-blocking', requestHostname) ) { result = 2; - if ( loggerEnabled ) { + if ( logger.enabled ) { logData = { engine: 'u', result: 2, - raw: `no-strict-blocking: ${µb.sessionSwitches.z} true` + raw: `no-strict-blocking: ${sessionSwitches.z} true` }; } } @@ -160,7 +166,7 @@ const onBeforeRootFrameRequest = function(fctxt) { // Temporarily whitelisted? if ( result === 0 && strictBlockBypasser.isBypassed(requestHostname) ) { result = 2; - if ( loggerEnabled ) { + if ( logger.enabled ) { logData = { engine: 'u', result: 2, @@ -171,7 +177,7 @@ const onBeforeRootFrameRequest = function(fctxt) { // Static filtering if ( result === 0 ) { - ({ result, logData } = shouldStrictBlock(fctxt, loggerEnabled)); + ({ result, logData } = shouldStrictBlock(fctxt, logger.enabled)); } const pageStore = µb.bindTabToPageStore(fctxt.tabId, 'beforeRequest'); @@ -180,7 +186,7 @@ const onBeforeRootFrameRequest = function(fctxt) { pageStore.journalAddRequest(fctxt, result); } - if ( loggerEnabled ) { + if ( logger.enabled ) { fctxt.setFilter(logData); } @@ -190,12 +196,12 @@ const onBeforeRootFrameRequest = function(fctxt) { result !== 1 && trusted === false && pageStore !== null && - µb.staticNetFilteringEngine.hasQuery(fctxt) + staticNetFilteringEngine.hasQuery(fctxt) ) { pageStore.redirectNonBlockedRequest(fctxt); } - if ( loggerEnabled ) { + if ( logger.enabled ) { fctxt.setRealm('network').toLogger(); } @@ -259,8 +265,7 @@ const onBeforeRootFrameRequest = function(fctxt) { // --------+--------+--------+--------+--------+--------+ const shouldStrictBlock = function(fctxt, loggerEnabled) { - const µb = µBlock; - const snfe = µb.staticNetFilteringEngine; + const snfe = staticNetFilteringEngine; // Explicit filtering: `document` option const rs = snfe.matchRequest(fctxt, 0b0011); @@ -358,7 +363,6 @@ const validateStrictBlock = function(fctxt, logData) { // Intercept and filter behind-the-scene requests. const onBeforeBehindTheSceneRequest = function(fctxt) { - const µb = µBlock; const pageStore = µb.pageStoreFromTabId(fctxt.tabId); if ( pageStore === null ) { return; } @@ -403,7 +407,7 @@ const onBeforeBehindTheSceneRequest = function(fctxt) { // https://github.com/uBlockOrigin/uBlock-issues/issues/1204 onBeforeBehindTheSceneRequest.journalAddRequest(fctxt, result); - if ( µb.logger.enabled ) { + if ( logger.enabled ) { fctxt.setRealm('network').toLogger(); } @@ -440,7 +444,7 @@ const onBeforeBehindTheSceneRequest = function(fctxt) { const gc = ( ) => { gcTimer = undefined; - if ( pageStoresToken !== µBlock.pageStoresToken ) { return reset(); } + if ( pageStoresToken !== µb.pageStoresToken ) { return reset(); } gcTimer = vAPI.setTimeout(gc, 30011); }; @@ -448,15 +452,15 @@ const onBeforeBehindTheSceneRequest = function(fctxt) { const docHostname = fctxt.getDocHostname(); if ( docHostname !== hostname || - pageStoresToken !== µBlock.pageStoresToken + pageStoresToken !== µb.pageStoresToken ) { hostname = docHostname; pageStores = new Set(); - for ( const pageStore of µBlock.pageStores.values() ) { + for ( const pageStore of µb.pageStores.values() ) { if ( pageStore.tabHostname !== docHostname ) { continue; } pageStores.add(pageStore); } - pageStoresToken = µBlock.pageStoresToken; + pageStoresToken = µb.pageStoresToken; if ( gcTimer !== undefined ) { clearTimeout(gcTimer); } @@ -486,7 +490,6 @@ const onHeadersReceived = function(details) { return; } - const µb = µBlock; const fctxt = µb.filteringContext.fromWebrequestDetails(details); const isRootDoc = fctxt.itype === fctxt.MAIN_FRAME; @@ -511,7 +514,7 @@ const onHeadersReceived = function(details) { if ( isRootDoc === false && µb.hiddenSettings.filterOnHeaders === true ) { const result = pageStore.filterOnHeaders(fctxt, responseHeaders); if ( result !== 0 ) { - if ( µb.logger.enabled ) { + if ( logger.enabled ) { fctxt.setRealm('network').toLogger(); } if ( result === 1 ) { @@ -542,7 +545,7 @@ const onHeadersReceived = function(details) { µb.canFilterResponseData && filterDocument(fctxt, details) === true; let modifiedHeaders = false; - if ( µb.httpheaderFilteringEngine.apply(fctxt, responseHeaders) === true ) { + if ( httpheaderFilteringEngine.apply(fctxt, responseHeaders) === true ) { modifiedHeaders = true; } if ( injectCSP(fctxt, pageStore, responseHeaders) === true ) { @@ -622,7 +625,6 @@ const normalizeBehindTheSceneResponseHeaders = function(details) { **/ const filterDocument = (( ) => { - const µb = µBlock; const filterers = new Map(); let domParser, xmlSerializer, utf8TextDecoder, textDecoder, textEncoder; @@ -749,7 +751,7 @@ const filterDocument = (( ) => { filterer.mime ); charsetFound = charsetFromDoc(doc); - charsetUsed = µb.textEncode.normalizeCharset(charsetFound); + charsetUsed = textEncode.normalizeCharset(charsetFound); if ( charsetUsed === undefined ) { return streamClose(filterer); } @@ -764,7 +766,7 @@ const filterDocument = (( ) => { // In case of no explicit charset found, try to find one again, but // this time with the whole document parsed. if ( charsetFound === undefined ) { - charsetFound = µb.textEncode.normalizeCharset(charsetFromDoc(doc)); + charsetFound = textEncode.normalizeCharset(charsetFromDoc(doc)); if ( charsetFound !== charsetUsed ) { if ( charsetFound === undefined ) { return streamClose(filterer); @@ -779,7 +781,7 @@ const filterDocument = (( ) => { let modified = false; if ( filterer.selectors !== undefined ) { - if ( µb.htmlFilteringEngine.apply(doc, filterer) ) { + if ( htmlFilteringEngine.apply(doc, filterer) ) { modified = true; } } @@ -799,7 +801,7 @@ const filterDocument = (( ) => { doc.documentElement.outerHTML ); if ( charsetUsed !== 'utf-8' ) { - encodedStream = µb.textEncode.encode( + encodedStream = textEncode.encode( charsetUsed, encodedStream ); @@ -837,7 +839,7 @@ const filterDocument = (( ) => { charset: undefined }; - request.selectors = µb.htmlFilteringEngine.retrieve(request); + request.selectors = htmlFilteringEngine.retrieve(request); if ( request.selectors === undefined ) { return; } const headers = extras.responseHeaders; @@ -847,7 +849,7 @@ const filterDocument = (( ) => { if ( request.mime === undefined ) { return; } let charset = charsetFromContentType(contentType); if ( charset !== undefined ) { - charset = µb.textEncode.normalizeCharset(charset); + charset = textEncode.normalizeCharset(charset); if ( charset === undefined ) { return; } request.charset = charset; } @@ -869,8 +871,6 @@ const filterDocument = (( ) => { /******************************************************************************/ const injectCSP = function(fctxt, pageStore, responseHeaders) { - const µb = µBlock; - const loggerEnabled = µb.logger.enabled; const cspSubsets = []; const requestType = fctxt.type; @@ -881,8 +881,8 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) { const builtinDirectives = []; if ( pageStore.filterScripting(fctxt, true) === 1 ) { - builtinDirectives.push(µBlock.cspNoScripting); - if ( loggerEnabled ) { + builtinDirectives.push(µb.cspNoScripting); + if ( logger.enabled ) { fctxt.setRealm('network').setType('scripting').toLogger(); } } @@ -896,9 +896,9 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) { fctxt2.setDocOriginFromURL(fctxt.url); const result = pageStore.filterRequest(fctxt2); if ( result === 1 ) { - builtinDirectives.push(µBlock.cspNoInlineScript); + builtinDirectives.push(µb.cspNoInlineScript); } - if ( result === 2 && loggerEnabled ) { + if ( result === 2 && logger.enabled ) { fctxt2.setRealm('network').toLogger(); } } @@ -907,8 +907,8 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) { // - Use a CSP to also forbid inline fonts if remote fonts are blocked. fctxt.type = 'inline-font'; if ( pageStore.filterRequest(fctxt) === 1 ) { - builtinDirectives.push(µBlock.cspNoInlineFont); - if ( loggerEnabled ) { + builtinDirectives.push(µb.cspNoInlineFont); + if ( logger.enabled ) { fctxt.setRealm('network').toLogger(); } } @@ -923,7 +923,7 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) { fctxt.type = requestType; const staticDirectives = - µb.staticNetFilteringEngine.matchAndFetchModifiers(fctxt, 'csp'); + staticNetFilteringEngine.matchAndFetchModifiers(fctxt, 'csp'); if ( staticDirectives !== undefined ) { for ( const directive of staticDirectives ) { if ( directive.result !== 1 ) { continue; } @@ -934,16 +934,16 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) { // URL filtering `allow` rules override static filtering. if ( cspSubsets.length !== 0 && - µb.sessionURLFiltering.evaluateZ( + sessionURLFiltering.evaluateZ( fctxt.getTabHostname(), fctxt.url, 'csp' ) === 2 ) { - if ( loggerEnabled ) { + if ( logger.enabled ) { fctxt.setRealm('network') .setType('csp') - .setFilter(µb.sessionURLFiltering.toLogData()) + .setFilter(sessionURLFiltering.toLogData()) .toLogger(); } return; @@ -953,16 +953,16 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) { if ( cspSubsets.length !== 0 && µb.userSettings.advancedUserEnabled && - µb.sessionFirewall.evaluateCellZY( + sessionFirewall.evaluateCellZY( fctxt.getTabHostname(), fctxt.getTabHostname(), '*' ) === 2 ) { - if ( loggerEnabled ) { + if ( logger.enabled ) { fctxt.setRealm('network') .setType('csp') - .setFilter(µb.sessionFirewall.toLogData()) + .setFilter(sessionFirewall.toLogData()) .toLogger(); } return; @@ -972,7 +972,7 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) { // Static CSP policies will be applied. - if ( loggerEnabled && staticDirectives !== undefined ) { + if ( logger.enabled && staticDirectives !== undefined ) { fctxt.setRealm('network') .pushFilters(staticDirectives.map(a => a.logData())) .toLogger(); @@ -1006,7 +1006,7 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) { const foilFloc = function(fctxt, responseHeaders) { const hn = fctxt.getHostname(); - if ( µBlock.scriptletFilteringEngine.hasScriptlet(hn, 1, 'no-floc') === false ) { + if ( scriptletFilteringEngine.hasScriptlet(hn, 1, 'no-floc') === false ) { return false; } responseHeaders.push({ @@ -1029,7 +1029,7 @@ const foilLargeMediaElement = function(details, fctxt, pageStore) { if ( details.fromCache === true ) { return; } let size = 0; - if ( µBlock.userSettings.largeMediaSize !== 0 ) { + if ( µb.userSettings.largeMediaSize !== 0 ) { const headers = details.responseHeaders; const i = headerIndexFromName('content-length', headers); if ( i === -1 ) { return; } @@ -1039,7 +1039,7 @@ const foilLargeMediaElement = function(details, fctxt, pageStore) { const result = pageStore.filterLargeMediaElement(fctxt, size); if ( result === 0 ) { return; } - if ( µBlock.logger.enabled ) { + if ( logger.enabled ) { fctxt.setRealm('network').toLogger(); } @@ -1083,14 +1083,14 @@ const strictBlockBypasser = { if ( typeof hostname !== 'string' || hostname === '' ) { return; } this.hostnameToDeadlineMap.set( hostname, - Date.now() + µBlock.hiddenSettings.strictBlockingBypassDuration * 1000 + Date.now() + µb.hiddenSettings.strictBlockingBypassDuration * 1000 ); }, isBypassed: function(hostname) { if ( this.hostnameToDeadlineMap.size === 0 ) { return false; } let bypassDuration = - µBlock.hiddenSettings.strictBlockingBypassDuration * 1000; + µb.hiddenSettings.strictBlockingBypassDuration * 1000; if ( this.cleanupTimer === undefined ) { this.cleanupTimer = vAPI.setTimeout( ( ) => { @@ -1122,9 +1122,7 @@ const strictBlockBypasser = { /******************************************************************************/ -// Export - -µBlock.webRequest = { +const webRequest = { start: (( ) => { vAPI.net = new vAPI.Net(); vAPI.net.suspend(); @@ -1147,3 +1145,7 @@ const strictBlockBypasser = { }; /******************************************************************************/ + +export { webRequest }; + +/******************************************************************************/ diff --git a/src/js/ublock.js b/src/js/ublock.js index bef20f8a3..d6567e545 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -23,8 +23,26 @@ /******************************************************************************/ +import contextMenu from './contextmenu.js'; +import cosmeticFilteringEngine from './cosmetic-filtering.js'; +import µb from './background.js'; import { hostnameFromURI } from './uri-utils.js'; -import µBlock from './background.js'; +import { redirectEngine } from './redirect-engine.js'; + +import { + permanentFirewall, + sessionFirewall, +} from './dynamic-net-filtering.js'; + +import { + permanentSwitches, + sessionSwitches, +} from './hnswitches.js'; + +import { + permanentURLFiltering, + sessionURLFiltering, +} from './url-net-filtering.js'; /******************************************************************************/ /******************************************************************************/ @@ -90,7 +108,7 @@ const matchBucket = function(url, hostname, bucket, start) { /******************************************************************************/ -µBlock.getNetFilteringSwitch = function(url) { +µb.getNetFilteringSwitch = function(url) { const hostname = hostnameFromURI(url); let key = hostname; for (;;) { @@ -109,7 +127,7 @@ const matchBucket = function(url, hostname, bucket, start) { /******************************************************************************/ -µBlock.toggleNetFilteringSwitch = function(url, scope, newState) { +µb.toggleNetFilteringSwitch = function(url, scope, newState) { const currentState = this.getNetFilteringSwitch(url); if ( newState === undefined ) { newState = !currentState; @@ -179,7 +197,7 @@ const matchBucket = function(url, hostname, bucket, start) { /******************************************************************************/ -µBlock.arrayFromWhitelist = function(whitelist) { +µb.arrayFromWhitelist = function(whitelist) { const out = new Set(); for ( const bucket of whitelist.values() ) { for ( const directive of bucket ) { @@ -189,13 +207,13 @@ const matchBucket = function(url, hostname, bucket, start) { return Array.from(out).sort((a, b) => a.localeCompare(b)); }; -µBlock.stringFromWhitelist = function(whitelist) { +µb.stringFromWhitelist = function(whitelist) { return this.arrayFromWhitelist(whitelist).join('\n'); }; /******************************************************************************/ -µBlock.whitelistFromArray = function(lines) { +µb.whitelistFromArray = function(lines) { const whitelist = new Map(); // Comment bucket must always be ready to be used. @@ -273,27 +291,27 @@ const matchBucket = function(url, hostname, bucket, start) { return whitelist; }; -µBlock.whitelistFromString = function(s) { +µb.whitelistFromString = function(s) { return this.whitelistFromArray(s.split('\n')); }; // https://github.com/gorhill/uBlock/issues/3717 -µBlock.reWhitelistBadHostname = /[^a-z0-9.\-_\[\]:]/; -µBlock.reWhitelistHostnameExtractor = /([a-z0-9.\-_\[\]]+)(?::[\d*]+)?\/(?:[^\x00-\x20\/]|$)[^\x00-\x20]*$/; +µb.reWhitelistBadHostname = /[^a-z0-9.\-_\[\]:]/; +µb.reWhitelistHostnameExtractor = /([a-z0-9.\-_\[\]]+)(?::[\d*]+)?\/(?:[^\x00-\x20\/]|$)[^\x00-\x20]*$/; /******************************************************************************/ -µBlock.changeUserSettings = function(name, value) { +µb.changeUserSettings = function(name, value) { let us = this.userSettings; // Return all settings if none specified. if ( name === undefined ) { us = JSON.parse(JSON.stringify(us)); - us.noCosmeticFiltering = this.sessionSwitches.evaluate('no-cosmetic-filtering', '*') === 1; - us.noLargeMedia = this.sessionSwitches.evaluate('no-large-media', '*') === 1; - us.noRemoteFonts = this.sessionSwitches.evaluate('no-remote-fonts', '*') === 1; - us.noScripting = this.sessionSwitches.evaluate('no-scripting', '*') === 1; - us.noCSPReports = this.sessionSwitches.evaluate('no-csp-reports', '*') === 1; + us.noCosmeticFiltering = sessionSwitches.evaluate('no-cosmetic-filtering', '*') === 1; + us.noLargeMedia = sessionSwitches.evaluate('no-large-media', '*') === 1; + us.noRemoteFonts = sessionSwitches.evaluate('no-remote-fonts', '*') === 1; + us.noScripting = sessionSwitches.evaluate('no-scripting', '*') === 1; + us.noCSPReports = sessionSwitches.evaluate('no-csp-reports', '*') === 1; return us; } @@ -338,11 +356,11 @@ const matchBucket = function(url, hostname, bucket, start) { break; case 'collapseBlocked': if ( value === false ) { - this.cosmeticFilteringEngine.removeFromSelectorCache('*', 'net'); + cosmeticFilteringEngine.removeFromSelectorCache('*', 'net'); } break; case 'contextMenuEnabled': - this.contextMenu.update(null); + contextMenu.update(null); break; case 'hyperlinkAuditingDisabled': if ( this.privacySettingsSupported ) { @@ -371,8 +389,8 @@ const matchBucket = function(url, hostname, bucket, start) { } if ( switchName === undefined ) { break; } let switchState = value ? 1 : 0; - this.sessionSwitches.toggle(switchName, '*', switchState); - if ( this.permanentSwitches.toggle(switchName, '*', switchState) ) { + sessionSwitches.toggle(switchName, '*', switchState); + if ( permanentSwitches.toggle(switchName, '*', switchState) ) { this.saveHostnameSwitches(); } break; @@ -399,13 +417,13 @@ const matchBucket = function(url, hostname, bucket, start) { // https://www.reddit.com/r/uBlockOrigin/comments/8524cf/my_custom_scriptlets_doesnt_work_what_am_i_doing/ -µBlock.changeHiddenSettings = function(hs) { +µb.changeHiddenSettings = function(hs) { const mustReloadResources = hs.userResourcesLocation !== this.hiddenSettings.userResourcesLocation; this.hiddenSettings = hs; this.saveHiddenSettings(); if ( mustReloadResources ) { - this.redirectEngine.invalidateResourcesSelfie(); + redirectEngine.invalidateResourcesSelfie(); this.loadRedirectResources(); } this.fireDOMEvent('hiddenSettingsChanged'); @@ -413,7 +431,7 @@ const matchBucket = function(url, hostname, bucket, start) { /******************************************************************************/ -µBlock.elementPickerExec = async function( +µb.elementPickerExec = async function( tabId, frameId, targetElement, @@ -451,18 +469,18 @@ const matchBucket = function(url, hostname, bucket, start) { // Always set own rules, trying to be fancy to avoid setting seemingly // (but not really) redundant rules led to this issue. -µBlock.toggleFirewallRule = function(details) { +µb.toggleFirewallRule = function(details) { let { srcHostname, desHostname, requestType, action } = details; if ( action !== 0 ) { - this.sessionFirewall.setCell( + sessionFirewall.setCell( srcHostname, desHostname, requestType, action ); } else { - this.sessionFirewall.unsetCell( + sessionFirewall.unsetCell( srcHostname, desHostname, requestType @@ -472,14 +490,14 @@ const matchBucket = function(url, hostname, bucket, start) { // https://github.com/chrisaljoudi/uBlock/issues/731#issuecomment-73937469 if ( details.persist ) { if ( action !== 0 ) { - this.permanentFirewall.setCell( + permanentFirewall.setCell( srcHostname, desHostname, requestType, action ); } else { - this.permanentFirewall.unsetCell( + permanentFirewall.unsetCell( srcHostname, desHostname, requestType, @@ -506,7 +524,7 @@ const matchBucket = function(url, hostname, bucket, start) { } // https://github.com/chrisaljoudi/uBlock/issues/420 - this.cosmeticFilteringEngine.removeFromSelectorCache(srcHostname, 'net'); + cosmeticFilteringEngine.removeFromSelectorCache(srcHostname, 'net'); if ( details.tabId === undefined ) { return; } @@ -525,8 +543,8 @@ const matchBucket = function(url, hostname, bucket, start) { /******************************************************************************/ -µBlock.toggleURLFilteringRule = function(details) { - let changed = this.sessionURLFiltering.setRule( +µb.toggleURLFilteringRule = function(details) { + let changed = sessionURLFiltering.setRule( details.context, details.url, details.type, @@ -534,11 +552,11 @@ const matchBucket = function(url, hostname, bucket, start) { ); if ( changed === false ) { return; } - this.cosmeticFilteringEngine.removeFromSelectorCache(details.context, 'net'); + cosmeticFilteringEngine.removeFromSelectorCache(details.context, 'net'); if ( details.persist !== true ) { return; } - changed = this.permanentURLFiltering.setRule( + changed = permanentURLFiltering.setRule( details.context, details.url, details.type, @@ -552,8 +570,8 @@ const matchBucket = function(url, hostname, bucket, start) { /******************************************************************************/ -µBlock.toggleHostnameSwitch = function(details) { - let changed = this.sessionSwitches.toggleZ( +µb.toggleHostnameSwitch = function(details) { + let changed = sessionSwitches.toggleZ( details.name, details.hostname, !!details.deep, @@ -582,7 +600,7 @@ const matchBucket = function(url, hostname, bucket, start) { if ( details.persist !== true ) { return; } - changed = this.permanentSwitches.toggleZ( + changed = permanentSwitches.toggleZ( details.name, details.hostname, !!details.deep, @@ -595,29 +613,28 @@ const matchBucket = function(url, hostname, bucket, start) { /******************************************************************************/ -µBlock.blockingModeFromHostname = function(hn) { +µb.blockingModeFromHostname = function(hn) { let bits = 0; - if ( this.sessionSwitches.evaluateZ('no-scripting', hn) ) { + if ( sessionSwitches.evaluateZ('no-scripting', hn) ) { bits |= 0b00000010; } if ( this.userSettings.advancedUserEnabled ) { - const fw = this.sessionFirewall; - if ( fw.evaluateCellZY(hn, '*', '3p') === 1 ) { + if ( sessionFirewall.evaluateCellZY(hn, '*', '3p') === 1 ) { bits |= 0b00000100; } - if ( fw.evaluateCellZY(hn, '*', '3p-script') === 1 ) { + if ( sessionFirewall.evaluateCellZY(hn, '*', '3p-script') === 1 ) { bits |= 0b00001000; } - if ( fw.evaluateCellZY(hn, '*', '3p-frame') === 1 ) { + if ( sessionFirewall.evaluateCellZY(hn, '*', '3p-frame') === 1 ) { bits |= 0b00010000; } } return bits; }; -µBlock.parseBlockingProfiles = (( ) => { +µb.parseBlockingProfiles = (( ) => { const parse = function() { - const s = µBlock.hiddenSettings.blockingProfiles; + const s = µb.hiddenSettings.blockingProfiles; const profiles = []; s.split(/\s+/).forEach(s => { let pos = s.indexOf('/'); @@ -629,8 +646,8 @@ const matchBucket = function(url, hostname, bucket, start) { const color = s.slice(pos + 1); profiles.push({ bits, color: color !== '' ? color : '#666' }); }); - µBlock.liveBlockingProfiles = profiles; - µBlock.blockingProfileColorCache.clear(); + µb.liveBlockingProfiles = profiles; + µb.blockingProfileColorCache.clear(); }; parse(); @@ -642,7 +659,7 @@ const matchBucket = function(url, hostname, bucket, start) { /******************************************************************************/ -µBlock.scriptlets = (function() { +µb.scriptlets = (function() { const pendingEntries = new Map(); const Entry = class { diff --git a/src/js/url-net-filtering.js b/src/js/url-net-filtering.js index c5f1e2283..7ee40a3b8 100644 --- a/src/js/url-net-filtering.js +++ b/src/js/url-net-filtering.js @@ -23,8 +23,8 @@ /******************************************************************************/ +import µb from './background.js'; import { LineIterator } from './text-iterators.js'; -import µBlock from './background.js'; /******************************************************************************* @@ -200,7 +200,7 @@ URLNetFiltering.prototype.evaluateZ = function(context, target, type) { if ( this.rules.size === 0 ) { return 0; } - µBlock.decomposeHostname(context, this.decomposedSource); + µb.decomposeHostname(context, this.decomposedSource); for ( let shn of this.decomposedSource ) { this.context = shn; let entries = this.rules.get(shn + ' ' + type); @@ -370,11 +370,9 @@ URLNetFiltering.prototype.removeFromRuleParts = function(parts) { /******************************************************************************/ -// Export +const sessionURLFiltering = new URLNetFiltering(); +const permanentURLFiltering = new URLNetFiltering(); -µBlock.URLNetFiltering = URLNetFiltering; - -µBlock.sessionURLFiltering = new URLNetFiltering(); -µBlock.permanentURLFiltering = new URLNetFiltering(); +export { permanentURLFiltering, sessionURLFiltering }; /******************************************************************************/ diff --git a/src/js/utils.js b/src/js/utils.js index 955ce8ceb..4308cdc0e 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -23,12 +23,13 @@ /******************************************************************************/ +import io from './assets.js'; +import µb from './background.js'; import { LineIterator } from './text-iterators.js'; -import µBlock from './background.js'; /******************************************************************************/ -µBlock.formatCount = function(count) { +µb.formatCount = function(count) { if ( typeof count !== 'number' ) { return ''; } @@ -53,7 +54,7 @@ import µBlock from './background.js'; /******************************************************************************/ -µBlock.dateNowToSensibleString = function() { +µb.dateNowToSensibleString = function() { const now = new Date(Date.now() - (new Date()).getTimezoneOffset() * 60000); return now.toISOString().replace(/\.\d+Z$/, '') .replace(/:/g, '.') @@ -62,7 +63,7 @@ import µBlock from './background.js'; /******************************************************************************/ -µBlock.openNewTab = function(details) { +µb.openNewTab = function(details) { if ( details.url.startsWith('logger-ui.html') ) { if ( details.shiftKey ) { this.changeUserSettings( @@ -92,7 +93,7 @@ import µBlock from './background.js'; /******************************************************************************/ -µBlock.MRUCache = class { +µb.MRUCache = class { constructor(size) { this.size = size; this.array = []; @@ -136,13 +137,13 @@ import µBlock from './background.js'; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions -µBlock.escapeRegex = function(s) { +µb.escapeRegex = function(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }; /******************************************************************************/ -µBlock.decomposeHostname = (( ) => { +µb.decomposeHostname = (( ) => { // For performance purpose, as simple tests as possible const reHostnameVeryCoarse = /[g-z_-]/; const reIPv4VeryCoarse = /\.\d+$/; @@ -196,7 +197,7 @@ import µBlock from './background.js'; // TODO: evaluate using TextEncoder/TextDecoder -µBlock.orphanizeString = function(s) { +µb.orphanizeString = function(s) { return JSON.parse(JSON.stringify(s)); }; @@ -217,10 +218,6 @@ import µBlock from './background.js'; // From uBO's dev console, launch the benchmark: // µBlock.staticNetFilteringEngine.benchmark(); // -// The advanced setting `consoleLogLevel` must be set to `info` to see the -// results in uBO's dev console, see: -// https://github.com/gorhill/uBlock/wiki/Advanced-settings#consoleloglevel -// // The usual browser dev tools can be used to obtain useful profiling // data, i.e. start the profiler, call the benchmark method from the // console, then stop the profiler when it completes. @@ -232,7 +229,7 @@ import µBlock from './background.js'; // Rename ./tmp/requests.json.gz to something else if you no longer want // ./assets/requests.json in the build. -µBlock.loadBenchmarkDataset = (( ) => { +µb.loadBenchmarkDataset = (( ) => { let datasetPromise; let ttlTimer; @@ -251,13 +248,13 @@ import µBlock from './background.js'; return datasetPromise; } - const datasetURL = µBlock.hiddenSettings.benchmarkDatasetURL; + const datasetURL = µb.hiddenSettings.benchmarkDatasetURL; if ( datasetURL === 'unset' ) { console.info(`No benchmark dataset available.`); return Promise.resolve(); } console.info(`Loading benchmark dataset...`); - datasetPromise = µBlock.assets.fetchText(datasetURL).then(details => { + datasetPromise = io.fetchText(datasetURL).then(details => { console.info(`Parsing benchmark dataset...`); const requests = []; const lineIter = new LineIterator(details.content); @@ -288,7 +285,7 @@ import µBlock from './background.js'; /******************************************************************************/ -µBlock.fireDOMEvent = function(name) { +µb.fireDOMEvent = function(name) { if ( window instanceof Object && window.dispatchEvent instanceof Function && @@ -302,7 +299,7 @@ import µBlock from './background.js'; // TODO: properly compare arrays -µBlock.getModifiedSettings = function(edit, orig = {}) { +µb.getModifiedSettings = function(edit, orig = {}) { const out = {}; for ( const prop in edit ) { if ( orig.hasOwnProperty(prop) && edit[prop] !== orig[prop] ) { @@ -312,7 +309,7 @@ import µBlock from './background.js'; return out; }; -µBlock.settingValueFromString = function(orig, name, s) { +µb.settingValueFromString = function(orig, name, s) { if ( typeof name !== 'string' || typeof s !== 'string' ) { return; } if ( orig.hasOwnProperty(name) === false ) { return; } let r;