From c7bab5502ed34c5e26cd72428a6c91d11b371030 Mon Sep 17 00:00:00 2001 From: gorhill Date: Mon, 23 Feb 2015 18:31:29 -0500 Subject: [PATCH] this mitigates #520, #376 --- platform/chromium/manifest.json | 2 +- src/js/3p-filters.js | 2 +- src/js/assets.js | 157 +++++++---- src/js/background.js | 7 +- src/js/contentscript-end.js | 1 - src/js/contentscript-start.js | 4 - src/js/cosmetic-filtering.js | 475 ++++++++++++++++--------------- src/js/messaging.js | 2 +- src/js/profiler.js | 3 +- src/js/start.js | 37 ++- src/js/static-net-filtering.js | 483 ++++++++++++++++++++------------ src/js/storage.js | 383 ++++++++++++++++++------- 12 files changed, 977 insertions(+), 579 deletions(-) diff --git a/platform/chromium/manifest.json b/platform/chromium/manifest.json index 0369bcd1d..5e2675492 100644 --- a/platform/chromium/manifest.json +++ b/platform/chromium/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "µBlock", - "version": "0.8.8.4", + "version": "0.8.8.5", "default_locale": "en", "description": "__MSG_extShortDesc__", diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js index 6745fcd22..d6c45949b 100644 --- a/src/js/3p-filters.js +++ b/src/js/3p-filters.js @@ -43,7 +43,7 @@ var re3rdPartyExternalAsset = /^https?:\/\/[a-z0-9]+/; var onMessage = function(msg) { switch ( msg.what ) { - case 'loadUbiquitousBlacklistCompleted': + case 'allFilterListsReloaded': renderBlacklists(); break; diff --git a/src/js/assets.js b/src/js/assets.js index 4f361dd2c..608763427 100644 --- a/src/js/assets.js +++ b/src/js/assets.js @@ -57,6 +57,7 @@ var thirdpartiesRepositoryRoot = 'https://raw.githubusercontent.com/gorhill/uAss var nullFunc = function() {}; var reIsExternalPath = /^[a-z]+:\/\//; var reIsUserPath = /^assets\/user\//; +var reIsCachePath = /^cache:\/\//; var lastRepoMetaTimestamp = 0; var lastRepoMetaIsRemote = false; var refreshRepoMetaPeriod = 5 * oneHour; @@ -197,17 +198,26 @@ var cachedAssetsManager = (function() { var cachedContentPath = cachedAssetPathPrefix + path; var bin = {}; bin[cachedContentPath] = content; + var removedItems = []; var onSaved = function() { var lastError = vAPI.lastError(); if ( lastError ) { details.error = 'Error: ' + lastError.message; console.error('µBlock> cachedAssetsManager.save():', details.error); cbError(details); - } else { - cbSuccess(details); + return; } + // Saving over an existing item must be seen as removing an + // existing item and adding a new one. + if ( typeof exports.onRemovedListener === 'function' ) { + exports.onRemovedListener(removedItems); + } + cbSuccess(details); }; var onEntries = function(entries) { + if ( entries.hasOwnProperty(path) ) { + removedItems.push(path); + } entries[path] = Date.now(); bin.cached_asset_entries = entries; vAPI.storage.set(bin, onSaved); @@ -218,6 +228,7 @@ var cachedAssetsManager = (function() { exports.remove = function(pattern, before) { var onEntries = function(entries) { var keystoRemove = []; + var removedItems = []; var paths = Object.keys(entries); var i = paths.length; var path; @@ -232,12 +243,16 @@ var cachedAssetsManager = (function() { if ( typeof before === 'number' && entries[path] >= before ) { continue; } + removedItems.push(path); keystoRemove.push(cachedAssetPathPrefix + path); delete entries[path]; } if ( keystoRemove.length ) { vAPI.storage.remove(keystoRemove); vAPI.storage.set({ 'cached_asset_entries': entries }); + if ( typeof exports.onRemovedListener === 'function' ) { + exports.onRemovedListener(removedItems); + } } }; getEntries(onEntries); @@ -245,8 +260,10 @@ var cachedAssetsManager = (function() { exports.removeAll = function(callback) { var onEntries = function() { + // Careful! do not remove 'assets/user/' exports.remove(/^https?:\/\/[a-z0-9]+/); exports.remove(/^assets\/(ublock|thirdparties)\//); + exports.remove(/^cache:\/\//); exports.remove('assets/checksums.txt'); if ( typeof callback === 'function' ) { callback(null); @@ -255,6 +272,8 @@ var cachedAssetsManager = (function() { getEntries(onEntries); }; + exports.onRemovedListener = null; + return exports; })(); @@ -263,15 +282,18 @@ var cachedAssetsManager = (function() { var getTextFileFromURL = function(url, onLoad, onError) { // https://github.com/gorhill/uMatrix/issues/15 var onResponseReceived = function() { - if ( this.status !== 0 && ( this.status < 200 || this.status >= 300 ) ) { + this.onload = this.onerror = this.ontimeout = null; + // xhr for local files gives status 0, but actually succeeds + var status = this.status || 200; + if ( status < 200 || status >= 300 ) { return onError.call(this); } - // xhr for local files gives status 0, but actually succeeds - if ( this.status === 0 && stringIsNotEmpty(this.responseText) === false ) { + // consider an empty result to be an error + if ( stringIsNotEmpty(this.responseText) === false ) { return onError.call(this); } // we never download anything else than plain text: discard if response - // appears to be a HTML document: could happen when server returns + // appears to be a HTML document: could happen when server serves // some kind of error page I suppose var text = this.responseText.trim(); if ( text.charAt(0) === '<' && text.slice(-1) === '>' ) { @@ -279,15 +301,23 @@ var getTextFileFromURL = function(url, onLoad, onError) { } return onLoad.call(this); }; + var onErrorReceived = function() { + this.onload = this.onerror = this.ontimeout = null; + onError.call(this); + }; // console.log('µBlock> getTextFileFromURL("%s"):', url); var xhr = new XMLHttpRequest(); - xhr.open('get', url, true); - xhr.timeout = 30000; - xhr.onload = onResponseReceived; - xhr.onerror = onError; - xhr.ontimeout = onError; - xhr.responseType = 'text'; - xhr.send(); + try { + xhr.open('get', url, true); + xhr.timeout = 30000; + xhr.onload = onResponseReceived; + xhr.onerror = onErrorReceived; + xhr.ontimeout = onErrorReceived; + xhr.responseType = 'text'; + xhr.send(); + } catch (e) { + onErrorReceived.call(xhr); + } }; /******************************************************************************/ @@ -454,13 +484,11 @@ var readLocalFile = function(path, callback) { }; var onInstallFileLoaded = function() { - this.onload = this.onerror = null; //console.log('µBlock> readLocalFile("%s") / onInstallFileLoaded()', path); reportBack(this.responseText); }; var onInstallFileError = function() { - this.onload = this.onerror = null; console.error('µBlock> readLocalFile("%s") / onInstallFileError()', path); reportBack('', 'Error'); }; @@ -512,7 +540,6 @@ var readRepoFile = function(path, callback) { var repositoryURL = projectRepositoryRoot + path; var onRepoFileLoaded = function() { - this.onload = this.onerror = null; //console.log('µBlock> readRepoFile("%s") / onRepoFileLoaded()', path); // https://github.com/gorhill/httpswitchboard/issues/263 if ( this.status === 200 ) { @@ -523,7 +550,6 @@ var readRepoFile = function(path, callback) { }; var onRepoFileError = function() { - this.onload = this.onerror = null; console.error(errorCantConnectTo.replace('{{url}}', repositoryURL)); reportBack('', 'Error'); }; @@ -568,13 +594,11 @@ var readRepoCopyAsset = function(path, callback) { }; var onInstallFileLoaded = function() { - this.onload = this.onerror = null; //console.log('µBlock> readRepoCopyAsset("%s") / onInstallFileLoaded()', path); reportBack(this.responseText); }; var onInstallFileError = function() { - this.onload = this.onerror = null; console.error('µBlock> readRepoCopyAsset("%s") / onInstallFileError():', path, this.statusText); reportBack('', 'Error'); }; @@ -593,7 +617,6 @@ var readRepoCopyAsset = function(path, callback) { var repositoryURLSkipCache = repositoryURL + '?ublock=' + Date.now(); var onRepoFileLoaded = function() { - this.onload = this.onerror = null; if ( stringIsNotEmpty(this.responseText) === false ) { console.error('µBlock> readRepoCopyAsset("%s") / onRepoFileLoaded("%s"): error', path, repositoryURL); cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError); @@ -605,13 +628,11 @@ var readRepoCopyAsset = function(path, callback) { }; var onRepoFileError = function() { - this.onload = this.onerror = null; console.error(errorCantConnectTo.replace('{{url}}', repositoryURL)); cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError); }; var onHomeFileLoaded = function() { - this.onload = this.onerror = null; if ( stringIsNotEmpty(this.responseText) === false ) { console.error('µBlock> readRepoCopyAsset("%s") / onHomeFileLoaded("%s"): no response', path, homeURL); // Fetch from repo only if obsolescence was due to repo checksum @@ -628,7 +649,6 @@ var readRepoCopyAsset = function(path, callback) { }; var onHomeFileError = function() { - this.onload = this.onerror = null; console.error(errorCantConnectTo.replace('{{url}}', homeURL)); // Fetch from repo only if obsolescence was due to repo checksum if ( assetEntry.localChecksum !== assetEntry.repoChecksum ) { @@ -715,13 +735,11 @@ var readRepoOnlyAsset = function(path, callback) { }; var onInstallFileLoaded = function() { - this.onload = this.onerror = null; //console.log('µBlock> readRepoOnlyAsset("%s") / onInstallFileLoaded()', path); reportBack(this.responseText); }; var onInstallFileError = function() { - this.onload = this.onerror = null; console.error('µBlock> readRepoOnlyAsset("%s") / onInstallFileError()', path); reportBack('', 'Error'); }; @@ -739,7 +757,6 @@ var readRepoOnlyAsset = function(path, callback) { var repositoryURL = projectRepositoryRoot + path + '?ublock=' + Date.now(); var onRepoFileLoaded = function() { - this.onload = this.onerror = null; if ( typeof this.responseText !== 'string' ) { console.error('µBlock> readRepoOnlyAsset("%s") / onRepoFileLoaded("%s"): no response', path, repositoryURL); cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError); @@ -757,7 +774,6 @@ var readRepoOnlyAsset = function(path, callback) { }; var onRepoFileError = function() { - this.onload = this.onerror = null; console.error(errorCantConnectTo.replace('{{url}}', repositoryURL)); cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError); }; @@ -827,7 +843,6 @@ var readExternalAsset = function(path, callback) { }; var onExternalFileLoaded = function() { - this.onload = this.onerror = null; // https://github.com/gorhill/uBlock/issues/708 // A successful download should never return an empty file: turn this // into an error condition. @@ -841,7 +856,6 @@ var readExternalAsset = function(path, callback) { }; var onExternalFileError = function() { - this.onload = this.onerror = null; console.error(errorCantConnectTo.replace('{{url}}', path)); cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError); }; @@ -874,12 +888,33 @@ var readExternalAsset = function(path, callback) { var readUserAsset = function(path, callback) { var onCachedContentLoaded = function(details) { - //console.log('µBlock> readUserAsset("%s") / onCachedContentLoaded()', path); + //console.log('µBlock.assets/readUserAsset("%s")/onCachedContentLoaded()', path); callback({ 'path': path, 'content': details.content }); }; var onCachedContentError = function() { - //console.log('µBlock> readUserAsset("%s") / onCachedContentError()', path); + //console.log('µBlock.assets/readUserAsset("%s")/onCachedContentError()', path); + callback({ 'path': path, 'content': '' }); + }; + + cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError); +}; + +/******************************************************************************/ + +// Asset available only from the cache. +// Cache data: +// Path --> starts with 'cache://' +// Cache --> whatever + +var readCacheAsset = function(path, callback) { + var onCachedContentLoaded = function(details) { + //console.log('µBlock.assets/readCacheAsset("%s")/onCachedContentLoaded()', path); + callback({ 'path': path, 'content': details.content }); + }; + + var onCachedContentError = function() { + //console.log('µBlock.assets/readCacheAsset("%s")/onCachedContentError()', path); callback({ 'path': path, 'content': '' }); }; @@ -930,6 +965,11 @@ exports.get = function(path, callback) { return; } + if ( reIsCachePath.test(path) ) { + readCacheAsset(path, callback); + return; + } + if ( reIsExternalPath.test(path) ) { readExternalAsset(path, callback); return; @@ -1042,14 +1082,20 @@ exports.purge = function(pattern, before) { cachedAssetsManager.remove(pattern, before); }; -/******************************************************************************/ - exports.purgeAll = function(callback) { cachedAssetsManager.removeAll(callback); }; /******************************************************************************/ +exports.onAssetCacheRemoved = { + addEventListener: function(callback) { + cachedAssetsManager.onRemovedListener = callback || null; + } +}; + +/******************************************************************************/ + return exports; })(); @@ -1076,8 +1122,9 @@ var updated = {}; var updatedCount = 0; var metadata = null; -var onStart = null; -var onCompleted = null; +var onStartListener = null; +var onCompletedListener = null; +var onAssetUpdatedListener = null; var exports = {}; @@ -1086,15 +1133,15 @@ var exports = {}; var onAssetUpdated = function(details) { var path = details.path; if ( details.error ) { - //console.debug('assets.js > µBlock.assetUpdater/onAssetUpdated: "%s" failed', path); + //console.debug('µBlock.assetUpdater/onAssetUpdated: "%s" failed', path); return; } - //console.debug('assets.js > µBlock.assetUpdater/onAssetUpdated: "%s"', path); + //console.debug('µBlock.assetUpdater/onAssetUpdated: "%s"', path); updated[path] = true; updatedCount += 1; - - // New data available: selfie is now invalid - µb.destroySelfie(); + if ( typeof onAssetUpdatedListener === 'function' ) { + onAssetUpdatedListener(details); + } }; /******************************************************************************/ @@ -1117,7 +1164,7 @@ var updateOne = function() { if ( !metaEntry.cacheObsolete && !metaEntry.repoObsolete ) { continue; } - //console.debug('assets.js > µBlock.assetUpdater/updateOne: assets.get("%s")', path); + //console.debug('µBlock.assetUpdater/updateOne: assets.get("%s")', path); µb.assets.get(path, onAssetUpdated); break; } @@ -1144,10 +1191,10 @@ var updateDaemon = function() { // Start an update cycle? if ( updateCycleTime !== 0 ) { if ( Date.now() >= updateCycleTime ) { - //console.debug('assets.js > µBlock.assetUpdater/updateDaemon: update cycle started'); + //console.debug('µBlock.assetUpdater/updateDaemon: update cycle started'); reset(); - if ( onStart !== null ) { - onStart(); + if ( typeof onStartListener === 'function' ) { + onStartListener(); } } return; @@ -1166,9 +1213,9 @@ var updateDaemon = function() { // If anything was updated, notify listener if ( updatedCount !== 0 ) { - if ( onCompleted !== null ) { - //console.debug('assets.js > µBlock.assetUpdater/updateDaemon: update cycle completed'); - onCompleted({ + if ( typeof onCompletedListener === 'function' ) { + //console.debug('µBlock.assetUpdater/updateDaemon: update cycle completed'); + onCompletedListener({ updated: JSON.parse(JSON.stringify(updated)), // give callee its own safe copy updatedCount: updatedCount }); @@ -1178,7 +1225,7 @@ var updateDaemon = function() { // Schedule next update cycle if ( updateCycleTime === 0 ) { reset(); - //console.debug('assets.js > µBlock.assetUpdater/updateDaemon: update cycle re-scheduled'); + //console.debug('µBlock.assetUpdater/updateDaemon: update cycle re-scheduled'); updateCycleTime = Date.now() + updateCycleNextPeriod; } }; @@ -1200,8 +1247,8 @@ var reset = function() { exports.onStart = { addEventListener: function(callback) { - onStart = callback || null; - if ( onStart !== null ) { + onStartListener = callback || null; + if ( typeof onStartListener === 'function' ) { updateCycleTime = Date.now() + updateCycleFirstPeriod; } } @@ -1209,9 +1256,17 @@ exports.onStart = { /******************************************************************************/ +exports.onAssetUpdated = { + addEventListener: function(callback) { + onAssetUpdatedListener = callback || null; + } +}; + +/******************************************************************************/ + exports.onCompleted = { addEventListener: function(callback) { - onCompleted = callback || null; + onCompletedListener = callback || null; } }; diff --git a/src/js/background.js b/src/js/background.js index 2fcf98b22..f121ea48b 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -83,6 +83,12 @@ return { allowedRequestCount: 0 }, + // read-only + systemSettings: { + compiledMagic: 'dgycowxrdjuf', + selfieMagic: 'dmakcrbecglp' + }, + // EasyList, EasyPrivacy and many others have an 4-day update period, // as per list headers. updateAssetsEvery: 97 * oneHour, @@ -111,7 +117,6 @@ return { remoteBlacklists: { }, - selfieMagic: 'bizhviclttie', selfieAfter: 23 * oneMinute, pageStores: {}, diff --git a/src/js/contentscript-end.js b/src/js/contentscript-end.js index d60ead379..956eb6d6f 100644 --- a/src/js/contentscript-end.js +++ b/src/js/contentscript-end.js @@ -334,7 +334,6 @@ var messager = vAPI.messaging.channel('contentscript-end.js'); hash = href.slice(pos + 3, pos + 11); selectors = generics[hash]; if ( selectors === undefined ) { continue; } - selectors = selectors.split(',\n'); iSelector = selectors.length; while ( iSelector-- ) { selector = selectors[iSelector]; diff --git a/src/js/contentscript-start.js b/src/js/contentscript-start.js index 01bb62d33..e2eb6f0a2 100644 --- a/src/js/contentscript-start.js +++ b/src/js/contentscript-start.js @@ -71,8 +71,6 @@ var cosmeticFilters = function(details) { var hide = details.cosmeticHide; var i; if ( donthide.length !== 0 ) { - donthide = donthide.length !== 1 ? donthide.join(',\n') : donthide[0]; - donthide = donthide.split(',\n'); i = donthide.length; while ( i-- ) { donthideCosmeticFilters[donthide[i]] = true; @@ -80,8 +78,6 @@ var cosmeticFilters = function(details) { } // https://github.com/gorhill/uBlock/issues/143 if ( hide.length !== 0 ) { - hide = hide.length !== 1 ? hide.join(',\n') : hide[0]; - hide = hide.split(',\n'); i = hide.length; var selector; while ( i-- ) { diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index 14c033fe3..c97de733a 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -481,7 +481,7 @@ var makeHash = function(unhide, token, mask) { if ( unhide !== 0 ) { hval |= 0x20000; } - return String.fromCharCode(hval >>> 9, hval & 0x1FF); + return hval.toString(36); }; /******************************************************************************/ @@ -503,8 +503,8 @@ var makeHash = function(unhide, token, mask) { // Specific filers can be enforced before the main document is loaded. var FilterContainer = function() { - this.domainHashMask = (1 << 10) - 1; - this.genericHashMask = (1 << 15) - 1; + this.domainHashMask = (1 << 10) - 1; // 10 bits + this.genericHashMask = (1 << 15) - 1; // 15 bits this.type0NoDomainHash = 'type0NoDomain'; this.type1NoDomainHash = 'type1NoDomain'; this.parser = new FilterParser(); @@ -521,23 +521,15 @@ var FilterContainer = function() { FilterContainer.prototype.reset = function() { this.parser.reset(); + this.µburi = µb.URI; this.frozen = false; this.acceptedCount = 0; this.duplicateCount = 0; + this.duplicateBuster = {}; this.selectorCache = {}; this.selectorCacheCount = 0; - // temporary (at parse time) - this.lowGenericHide = {}; - this.lowGenericDonthide = {}; - this.highGenericHide = {}; - this.highGenericDonthide = {}; - this.hostnameHide = {}; - this.hostnameDonthide = {}; - this.entityHide = {}; - this.entityDonthide = {}; - // permanent // [class], [id] this.lowGenericFilters = {}; @@ -555,9 +547,11 @@ FilterContainer.prototype.reset = function() { this.highMediumGenericDonthideCount = 0; // everything else + this.highHighGenericHideArray = []; this.highHighGenericHide = ''; - this.highHighGenericDonthide = ''; this.highHighGenericHideCount = 0; + this.highHighGenericDonthideArray = []; + this.highHighGenericDonthide = ''; this.highHighGenericDonthideCount = 0; // hostname, entity-based filters @@ -567,7 +561,7 @@ FilterContainer.prototype.reset = function() { /******************************************************************************/ -FilterContainer.prototype.add = function(s) { +FilterContainer.prototype.compile = function(s, out) { var parsed = this.parser.parse(s); if ( parsed.invalid ) { return false; @@ -576,271 +570,284 @@ FilterContainer.prototype.add = function(s) { var hostnames = parsed.hostnames; var i = hostnames.length; if ( i === 0 ) { - this.addGenericSelector(parsed); - return true; - } - // https://github.com/gorhill/uBlock/issues/151 - // Negated hostname means the filter applies to all non-negated hostnames - // of same filter OR globally if there is no non-negated hostnames. - var applyGlobally = true; - var hostname; - while ( i-- ) { - hostname = hostnames[i]; - if ( hostname.charAt(0) !== '~' ) { - applyGlobally = false; + this.compileGenericSelector(parsed, out); + } else { + // https://github.com/gorhill/uBlock/issues/151 + // Negated hostname means the filter applies to all non-negated hostnames + // of same filter OR globally if there is no non-negated hostnames. + var applyGlobally = true; + var hostname; + while ( i-- ) { + hostname = hostnames[i]; + if ( hostname.charAt(0) !== '~' ) { + applyGlobally = false; + } + if ( hostname.slice(-2) === '.*' ) { + this.compileEntitySelector(hostname, parsed, out); + } else { + this.compileHostnameSelector(hostname, parsed, out); + } } - if ( hostname.slice(-2) === '.*' ) { - this.addEntitySelector(hostname, parsed); - } else { - this.addHostnameSelector(hostname, parsed); + if ( applyGlobally ) { + this.compileGenericSelector(parsed, out); } } - if ( applyGlobally ) { - this.addGenericSelector(parsed); - } + return true; }; /******************************************************************************/ -FilterContainer.prototype.addGenericSelector = function(parsed) { - var entries; - var selectorType = parsed.suffix.charAt(0); - if ( selectorType === '#' || selectorType === '.' ) { - entries = parsed.unhide === 0 ? - this.lowGenericHide : - this.lowGenericDonthide; - } else { - entries = parsed.unhide === 0 ? - this.highGenericHide : - this.highGenericDonthide; +FilterContainer.prototype.compileGenericSelector = function(parsed, out) { + var selector = parsed.suffix; + var type = selector.charAt(0); + var matches; + + if ( type === '#' || type === '.' ) { + matches = this.rePlainSelector.exec(selector); + if ( matches === null ) { + return; + } + out.push( + 'c\v' + + (matches[1] === selector ? 'lg\v' : 'lg+\v') + + makeHash(parsed.unhide, matches[1], this.genericHashMask) + '\v' + + selector + ); + return; } - if ( entries[parsed.suffix] === undefined ) { - entries[parsed.suffix] = true; - } else { - //console.log('cosmetic-filtering.js > FilterContainer.addGenericSelector(): duplicate filter "%s"', parsed.suffix); - this.duplicateCount += 1; + + // ["title"] and ["alt"] will go in high-low generic bin. + if ( this.reHighLow.test(selector) ) { + out.push( + 'c\v' + + (parsed.unhide === 0 ? 'hlg0\v' : 'hlg1\v') + + selector + ); + return; } - this.acceptedCount += 1; + + // [href^="..."] will go in high-medium generic bin. + matches = this.reHighMedium.exec(selector); + if ( matches && matches.length === 2 ) { + out.push( + 'c\v' + + (parsed.unhide === 0 ? 'hmg0\v' : 'hmg1\v') + + matches[1] + '\v' + + selector + ); + return; + } + + // All else + out.push( + 'c\v' + + (parsed.unhide === 0 ? 'hhg0\v' : 'hhg1\v') + + selector + ); }; +FilterContainer.prototype.rePlainSelector = /^([#.][\w-]+)/; +FilterContainer.prototype.reHighLow = /^[a-z]*\[(?:alt|title)="[^"]+"\]$/; +FilterContainer.prototype.reHighMedium = /^\[href\^="https?:\/\/([^"]{8})[^"]*"\]$/; + /******************************************************************************/ -FilterContainer.prototype.addHostnameSelector = function(hostname, parsed) { +FilterContainer.prototype.compileHostnameSelector = function(hostname, parsed, out) { // https://github.com/gorhill/uBlock/issues/145 var unhide = parsed.unhide; if ( hostname.charAt(0) === '~' ) { hostname = hostname.slice(1); unhide ^= 1; } - var entries = unhide === 0 ? - this.hostnameHide : - this.hostnameDonthide; - var entry = entries[hostname]; - if ( entry === undefined ) { - entry = entries[hostname] = {}; - entry[parsed.suffix] = true; - } else if ( entry[parsed.suffix] === undefined ) { - entry[parsed.suffix] = true; + // https://github.com/gorhill/uBlock/issues/188 + // If not a real domain as per PSL, assign a synthetic one + var hash; + var domain = this.µburi.domainFromHostname(hostname); + if ( domain === '' ) { + hash = unhide === 0 ? this.type0NoDomainHash : this.type1NoDomainHash; } else { - //console.log('cosmetic-filtering.js > FilterContainer.addHostnameSelector(): duplicate filter "%s"', parsed.suffix); - this.duplicateCount += 1; + hash = makeHash(unhide, domain, this.domainHashMask); } - this.acceptedCount += 1; + out.push( + 'c\v' + + 'h\v' + + hash + '\v' + + hostname + '\v' + + parsed.suffix + ); }; /******************************************************************************/ -FilterContainer.prototype.addEntitySelector = function(hostname, parsed) { - var entries = parsed.unhide === 0 ? - this.entityHide : - this.entityDonthide; +FilterContainer.prototype.compileEntitySelector = function(hostname, parsed, out) { var entity = hostname.slice(0, -2); - var entry = entries[entity]; - if ( entry === undefined ) { - entry = entries[entity] = {}; - entry[parsed.suffix] = true; - } else if ( entry[parsed.suffix] === undefined ) { - entry[parsed.suffix] = true; - } else { - //console.log('cosmetic-filtering.js > FilterContainer.addEntitySelector(): duplicate filter "%s"', parsed.suffix); - this.duplicateCount += 1; - } - this.acceptedCount += 1; + out.push( + 'c\v' + + 'e\v' + + entity + '\v' + + parsed.suffix + ); }; /******************************************************************************/ -FilterContainer.prototype.freezeLowGenerics = function(what, type) { - var selectors = this[what]; - var matches, selectorPrefix, f, hash, bucket; - for ( var selector in selectors ) { - if ( selectors.hasOwnProperty(selector) === false ) { - continue; - } - matches = this.rePlainSelector.exec(selector); - if ( !matches ) { - continue; - } - selectorPrefix = matches[1]; - f = selectorPrefix === selector ? - new FilterPlain(selector) : - new FilterPlainMore(selector); - hash = makeHash(type, selectorPrefix, this.genericHashMask); - bucket = this.lowGenericFilters[hash]; - if ( bucket === undefined ) { - this.lowGenericFilters[hash] = f; - } else if ( bucket instanceof FilterBucket ) { - bucket.add(f); - } else { - this.lowGenericFilters[hash] = new FilterBucket(bucket, f); - } +FilterContainer.prototype.fromCompiledContent = function(text, lineBeg, skip) { + if ( skip ) { + return this.skipCompiledContent(text, lineBeg); } - this[what] = {}; -}; -FilterContainer.prototype.rePlainSelector = /^([#.][\w-]+)/; + var lineEnd; + var textEnd = text.length; + var line, fields, filter, bucket; -/******************************************************************************/ + while ( lineBeg < textEnd ) { + if ( text.charAt(lineBeg) !== 'c' ) { + return lineBeg; + } + lineEnd = text.indexOf('\n', lineBeg); + if ( lineEnd === -1 ) { + lineEnd = textEnd; + } + line = text.slice(lineBeg + 2, lineEnd); + lineBeg = lineEnd + 1; -FilterContainer.prototype.freezeHostnameSpecifics = function(what, type) { - var µburi = µb.URI; - var entries = this[what]; - var filters = this.hostnameFilters; - var f, domain, hash, bucket; - for ( var hostname in entries ) { - if ( entries.hasOwnProperty(hostname) === false ) { + + this.acceptedCount += 1; + if ( this.duplicateBuster.hasOwnProperty(line) ) { + this.duplicateCount += 1; continue; } - f = new FilterHostname(Object.keys(entries[hostname]).join(',\n'), hostname); - // https://github.com/gorhill/uBlock/issues/188 - // If not a real domain as per PSL, assign a synthetic one - domain = µburi.domainFromHostname(hostname); - if ( domain === '' ) { - hash = type === 0 ? this.type0NoDomainHash : this.type1NoDomainHash; - } else { - hash = makeHash(type, domain, this.domainHashMask); - } - bucket = filters[hash]; - if ( bucket === undefined ) { - filters[hash] = f; - } else if ( bucket instanceof FilterBucket ) { - bucket.add(f); - } else { - filters[hash] = new FilterBucket(bucket, f); - } - } - this[what] = {}; -}; + this.duplicateBuster[line] = true; -/******************************************************************************/ + fields = line.split('\v'); -FilterContainer.prototype.freezeEntitySpecifics = function(what, type) { - var entries = this[what]; - var filters = this.entityFilters; - var f, hash, bucket; - for ( var entity in entries ) { - if ( entries.hasOwnProperty(entity) === false ) { - continue; - } - f = new FilterEntity(Object.keys(entries[entity]).join(',\n'), entity); - hash = makeHash(type, entity, this.domainHashMask); - bucket = filters[hash]; - if ( bucket === undefined ) { - filters[hash] = f; - } else if ( bucket instanceof FilterBucket ) { - bucket.add(f); - } else { - filters[hash] = new FilterBucket(bucket, f); - } - } - this[what] = {}; -}; - -/******************************************************************************/ - -FilterContainer.prototype.freezeHighGenerics = function(what) { - var selectors = this['highGeneric' + what]; - - // ["title"] and ["alt"] will go in high-low generic bin. - var highLowGenericProp = 'highLowGeneric' + what; - var highLowGeneric = this[highLowGenericProp]; - var highLowGenericCount = 0; - - // [href^="..."] will go in high-medium generic bin. - var highMediumGenericProp = 'highMediumGeneric' + what; - var highMediumGeneric = this[highMediumGenericProp]; - var highMediumGenericCount = 0; - - // The rest will be put in the high-high generic bin. - // https://github.com/gorhill/uBlock/issues/236 - // Insert whatever we already have - var highHighGeneric = []; - var highHighGenericProp = 'highHighGeneric' + what; - if ( this[highHighGenericProp] !== '' ) { - highHighGeneric.push(this[highHighGenericProp]); - } - var highHighGenericCount = 0; - - // https://github.com/gorhill/uBlock/issues/456 - // Include tag name, it's part of the filter - var reHighLow = /^[a-z]*\[(?:alt|title)="[^"]+"\]$/; - var reHighMedium = /^\[href\^="https?:\/\/([^"]{8})[^"]*"\]$/; - var matches, hash; - - for ( var selector in selectors ) { - if ( selectors.hasOwnProperty(selector) === false ) { - continue; - } - // ["title"] and ["alt"] will go in high-low generic bin. - matches = reHighLow.exec(selector); - if ( matches && matches.length === 1 ) { - highLowGeneric[matches[0]] = true; - highLowGenericCount += 1; - continue; - } - // [href^="..."] will go in high-medium generic bin. - matches = reHighMedium.exec(selector); - if ( matches && matches.length === 2 ) { - hash = matches[1]; - if ( highMediumGeneric[hash] === undefined ) { - highMediumGeneric[hash] = matches[0]; + // h ir twitter.com .promoted-tweet + if ( fields[0] === 'h' ) { + filter = new FilterHostname(fields[3], fields[2]); + bucket = this.hostnameFilters[fields[1]]; + if ( bucket === undefined ) { + this.hostnameFilters[fields[1]] = filter; + } else if ( bucket instanceof FilterBucket ) { + bucket.add(filter); } else { - highMediumGeneric[hash] += ',\n' + matches[0]; + this.hostnameFilters[fields[1]] = new FilterBucket(bucket, filter); } - highMediumGenericCount += 1; continue; } - // All else - highHighGeneric.push(selector); - highHighGenericCount += 1; + + // lg 105 .largeAd + // lg+ 2jx .Mpopup + #Mad > #MadZone + if ( fields[0] === 'lg' || fields[0] === 'lg+' ) { + filter = fields[0] === 'lg' ? + new FilterPlain(fields[2]) : + new FilterPlainMore(fields[2]); + bucket = this.lowGenericFilters[fields[1]]; + if ( bucket === undefined ) { + this.lowGenericFilters[fields[1]] = filter; + } else if ( bucket instanceof FilterBucket ) { + bucket.add(filter); + } else { + this.lowGenericFilters[fields[1]] = new FilterBucket(bucket, filter); + } + continue; + } + + // entity selector + if ( fields[0] === 'e' ) { + bucket = this.entityFilters[fields[1]]; + if ( bucket === undefined ) { + this.entityFilters[fields[1]] = [fields[2]]; + } else { + bucket.push(fields[2]); + } + continue; + } + + if ( fields[0] === 'hlg0' ) { + this.highLowGenericHide[fields[1]] = true; + this.highLowGenericHideCount += 1; + continue; + } + + if ( fields[0] === 'hlg1' ) { + this.highLowGenericDonthide[fields[1]] = true; + this.highLowGenericDonthideCount += 1; + continue; + } + + if ( fields[0] === 'hmg0' ) { + if ( Array.isArray(this.highMediumGenericHide[fields[1]]) ) { + this.highMediumGenericHide[fields[1]].push(fields[2]); + } else { + this.highMediumGenericHide[fields[1]] = [fields[2]]; + } + this.highMediumGenericHideCount += 1; + continue; + } + + if ( fields[0] === 'hmg1' ) { + if ( Array.isArray(this.highMediumGenericDonthide[fields[1]]) ) { + this.highMediumGenericDonthide[fields[1]].push(fields[2]); + } else { + this.highMediumGenericDonthide[fields[1]] = [fields[2]]; + } + this.highMediumGenericDonthideCount += 1; + continue; + } + + if ( fields[0] === 'hhg0' ) { + this.highHighGenericHideArray.push(fields[1]); + this.highHighGenericHideCount += 1; + continue; + } + + if ( fields[0] === 'hhg1' ) { + this.highHighGenericDonthideArray.push(fields[1]); + this.highHighGenericDonthideCount += 1; + continue; + } } + return textEnd; +}; - this[highLowGenericProp + 'Count'] += highLowGenericCount; - this[highMediumGenericProp + 'Count'] += highMediumGenericCount; - this[highHighGenericProp] = highHighGeneric.join(',\n'); - this[highHighGenericProp + 'Count'] += highHighGenericCount; +/******************************************************************************/ - // Empty cumulated selectors - this['highGeneric' + what] = {}; +FilterContainer.prototype.skipCompiledContent = function(text, lineBeg) { + var lineEnd; + var textEnd = text.length; + + while ( lineBeg < textEnd ) { + if ( text.charAt(lineBeg) !== 'c' ) { + return lineBeg; + } + lineEnd = text.indexOf('\n', lineBeg); + if ( lineEnd === -1 ) { + lineEnd = textEnd; + } + lineBeg = lineEnd + 1; + } + return textEnd; }; /******************************************************************************/ FilterContainer.prototype.freeze = function() { - this.freezeLowGenerics('lowGenericHide', 0); - this.freezeLowGenerics('lowGenericDonthide', 1); - this.freezeHighGenerics('Hide'); - this.freezeHighGenerics('Donthide'); - this.freezeHostnameSpecifics('hostnameHide', 0); - this.freezeHostnameSpecifics('hostnameDonthide', 1); - this.freezeEntitySpecifics('entityHide', 0); - this.freezeEntitySpecifics('entityDonthide', 1); + this.duplicateBuster = {}; + + if ( this.highHighGenericHide !== '' ) { + this.highHighGenericHideArray.unshift(this.highHighGenericHide); + } + this.highHighGenericHide = this.highHighGenericHideArray.join(',\n'); + this.highHighGenericHideArray = []; + if ( this.highHighGenericDonthide !== '' ) { + this.highHighGenericDonthideArray.unshift(this.highHighGenericDonthide); + } + this.highHighGenericDonthide = this.highHighGenericDonthideArray.join(',\n'); + this.highHighGenericDonthideArray = []; + this.parser.reset(); this.frozen = true; - - //histogram('lowGenericFilters', this.lowGenericFilters); - //histogram('hostnameFilters', this.hostnameFilters); }; /******************************************************************************/ @@ -875,7 +882,7 @@ FilterContainer.prototype.toSelfie = function() { acceptedCount: this.acceptedCount, duplicateCount: this.duplicateCount, hostnameSpecificFilters: selfieFromDict(this.hostnameFilters), - entitySpecificFilters: selfieFromDict(this.entityFilters), + entitySpecificFilters: this.entityFilters, lowGenericFilters: selfieFromDict(this.lowGenericFilters), highLowGenericHide: this.highLowGenericHide, highLowGenericDonthide: this.highLowGenericDonthide, @@ -940,7 +947,7 @@ FilterContainer.prototype.fromSelfie = function(selfie) { this.acceptedCount = selfie.acceptedCount; this.duplicateCount = selfie.duplicateCount; this.hostnameFilters = dictFromSelfie(selfie.hostnameSpecificFilters); - this.entityFilters = dictFromSelfie(selfie.entitySpecificFilters); + this.entityFilters = selfie.entitySpecificFilters; this.lowGenericFilters = dictFromSelfie(selfie.lowGenericFilters); this.highLowGenericHide = selfie.highLowGenericHide; this.highLowGenericDonthide = selfie.highLowGenericDonthide; @@ -1156,14 +1163,18 @@ FilterContainer.prototype.retrieveDomainSelectors = function(request) { if ( bucket = this.hostnameFilters[this.type0NoDomainHash] ) { bucket.retrieve(hostname, r.cosmeticHide); } - hash = makeHash(0, r.entity, this.domainHashMask); - if ( bucket = this.entityFilters[hash] ) { - bucket.retrieve(pos === -1 ? domain : hostname.slice(0, pos - domain.length), r.cosmeticHide); + + // entity filter buckets are always plain js array + if ( bucket = this.entityFilters[r.entity] ) { + r.cosmeticHide = r.cosmeticHide.concat(bucket); } + // No entity exceptions as of now + hash = makeHash(1, domain, this.domainHashMask); if ( bucket = this.hostnameFilters[hash] ) { bucket.retrieve(hostname, r.cosmeticDonthide); } + // https://github.com/gorhill/uBlock/issues/188 // Special bucket for those filters without a valid domain name as per PSL if ( bucket = this.hostnameFilters[this.type1NoDomainHash] ) { diff --git a/src/js/messaging.js b/src/js/messaging.js index 1b9695ae2..e818b35c1 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -68,7 +68,7 @@ var onMessage = function(request, sender, callback) { break; case 'reloadAllFilters': - µb.reloadPresetBlacklists(request.switches, request.update); + µb.reloadFilterLists(request.switches, request.update); break; case 'reloadTab': diff --git a/src/js/profiler.js b/src/js/profiler.js index 27fc0ed80..20244d053 100644 --- a/src/js/profiler.js +++ b/src/js/profiler.js @@ -20,11 +20,12 @@ */ /* exported quickProfiler */ -'use strict'; /******************************************************************************/ var quickProfiler = (function() { + 'use strict'; + var timer = window.performance || Date; var time = 0; var count = 0; diff --git a/src/js/start.js b/src/js/start.js index 45d20d996..fee730fc7 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -27,6 +27,8 @@ (function() { +quickProfiler.start('start.js'); + /******************************************************************************/ // Final initialization steps after all needed assets are in memory. @@ -40,11 +42,15 @@ var onAllReady = function() { // Check for updates not too far in the future. µb.assetUpdater.onStart.addEventListener(µb.updateStartHandler.bind(µb)); µb.assetUpdater.onCompleted.addEventListener(µb.updateCompleteHandler.bind(µb)); + µb.assetUpdater.onAssetUpdated.addEventListener(µb.assetUpdatedHandler.bind(µb)); + µb.assets.onAssetCacheRemoved.addEventListener(µb.assetCacheRemovedHandler.bind(µb)); // Important: remove barrier to remote fetching, this was useful only // for launch time. µb.assets.allowRemoteFetch = true; + quickProfiler.stop(0); + vAPI.onLoadAllCompleted(); }; @@ -155,9 +161,34 @@ var onUserSettingsReady = function(userSettings) { µb.XAL.keyvalRemoveOne('logRequests'); }; -µBlock.loadUserSettings(onUserSettingsReady); -µBlock.loadWhitelist(onWhitelistReady); -µBlock.loadLocalSettings(); +/******************************************************************************/ + +// Housekeeping, as per system setting changes + +var onSystemSettingsReady = function(system) { + var µb = µBlock; + + var mustSaveSystemSettings = false; + if ( system.compiledMagic !== µb.systemSettings.compiledMagic ) { + µb.assets.purge(/^cache:\/\/compiled-/); + mustSaveSystemSettings = true; + } + if ( system.selfieMagic !== µb.systemSettings.selfieMagic ) { + µb.destroySelfie(); + mustSaveSystemSettings = true; + } + if ( mustSaveSystemSettings ) { + µb.saveSystemSettings(); + } + + µb.loadUserSettings(onUserSettingsReady); + µb.loadWhitelist(onWhitelistReady); + µb.loadLocalSettings(); +}; + +/******************************************************************************/ + +µBlock.loadSystemSettings(onSystemSettingsReady); /******************************************************************************/ diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 54b3b6b6e..99e3f8202 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -227,7 +227,7 @@ FilterPlain.prototype.match = function(url, tokenBeg) { return url.substr(tokenBeg - this.tokenBeg, this.s.length) === this.s; }; -FilterPlain.prototype.fid = 'a'; +FilterPlain.fid = FilterPlain.prototype.fid = 'a'; FilterPlain.prototype.toString = function() { return this.s; @@ -238,6 +238,10 @@ FilterPlain.prototype.toSelfie = function() { this.tokenBeg; }; +FilterPlain.compile = function(details) { + return details.f + '\t' + details.tokenBeg; +}; + FilterPlain.fromSelfie = function(s) { var pos = s.indexOf('\t'); return new FilterPlain(s.slice(0, pos), atoi(s.slice(pos + 1))); @@ -256,7 +260,7 @@ FilterPlainHostname.prototype.match = function(url, tokenBeg) { url.substr(tokenBeg - this.tokenBeg, this.s.length) === this.s; }; -FilterPlainHostname.prototype.fid = 'ah'; +FilterPlainHostname.fid = FilterPlainHostname.prototype.fid = 'ah'; FilterPlainHostname.prototype.toString = function() { return this.s + '$domain=' + this.hostname; @@ -268,6 +272,12 @@ FilterPlainHostname.prototype.toSelfie = function() { this.hostname; }; +FilterPlainHostname.compile = function(details, hostname) { + return details.f + '\t' + + details.tokenBeg + '\t' + + hostname; +}; + FilterPlainHostname.fromSelfie = function(s) { var args = s.split('\t'); return new FilterPlainHostname(args[0], atoi(args[1]), args[2]); @@ -283,7 +293,7 @@ FilterPlainPrefix0.prototype.match = function(url, tokenBeg) { return url.substr(tokenBeg, this.s.length) === this.s; }; -FilterPlainPrefix0.prototype.fid = '0a'; +FilterPlainPrefix0.fid = FilterPlainPrefix0.prototype.fid = '0a'; FilterPlainPrefix0.prototype.toString = function() { return this.s; @@ -293,6 +303,10 @@ FilterPlainPrefix0.prototype.toSelfie = function() { return this.s; }; +FilterPlainPrefix0.compile = function(details) { + return details.f; +}; + FilterPlainPrefix0.fromSelfie = function(s) { return new FilterPlainPrefix0(s); }; @@ -309,7 +323,7 @@ FilterPlainPrefix0Hostname.prototype.match = function(url, tokenBeg) { url.substr(tokenBeg, this.s.length) === this.s; }; -FilterPlainPrefix0Hostname.prototype.fid = '0ah'; +FilterPlainPrefix0Hostname.fid = FilterPlainPrefix0Hostname.prototype.fid = '0ah'; FilterPlainPrefix0Hostname.prototype.toString = function() { return this.s + '$domain=' + this.hostname; @@ -320,6 +334,10 @@ FilterPlainPrefix0Hostname.prototype.toSelfie = function() { this.hostname; }; +FilterPlainPrefix0Hostname.compile = function(details, hostname) { + return details.f + '\t' + hostname; +}; + FilterPlainPrefix0Hostname.fromSelfie = function(s) { var pos = s.indexOf('\t'); return new FilterPlainPrefix0Hostname(s.slice(0, pos), s.slice(pos + 1)); @@ -335,7 +353,7 @@ FilterPlainPrefix1.prototype.match = function(url, tokenBeg) { return url.substr(tokenBeg - 1, this.s.length) === this.s; }; -FilterPlainPrefix1.prototype.fid = '1a'; +FilterPlainPrefix1.fid = FilterPlainPrefix1.prototype.fid = '1a'; FilterPlainPrefix1.prototype.toString = function() { return this.s; @@ -345,6 +363,10 @@ FilterPlainPrefix1.prototype.toSelfie = function() { return this.s; }; +FilterPlainPrefix1.compile = function(details) { + return details.f; +}; + FilterPlainPrefix1.fromSelfie = function(s) { return new FilterPlainPrefix1(s); }; @@ -361,7 +383,7 @@ FilterPlainPrefix1Hostname.prototype.match = function(url, tokenBeg) { url.substr(tokenBeg - 1, this.s.length) === this.s; }; -FilterPlainPrefix1Hostname.prototype.fid = '1ah'; +FilterPlainPrefix1Hostname.fid = FilterPlainPrefix1Hostname.prototype.fid = '1ah'; FilterPlainPrefix1Hostname.prototype.toString = function() { return this.s + '$domain=' + this.hostname; @@ -372,6 +394,10 @@ FilterPlainPrefix1Hostname.prototype.toSelfie = function() { this.hostname; }; +FilterPlainPrefix1Hostname.compile = function(details, hostname) { + return details.f + '\t' + hostname; +}; + FilterPlainPrefix1Hostname.fromSelfie = function(s) { var pos = s.indexOf('\t'); return new FilterPlainPrefix1Hostname(s.slice(0, pos), s.slice(pos + 1)); @@ -387,7 +413,7 @@ FilterPlainLeftAnchored.prototype.match = function(url) { return url.slice(0, this.s.length) === this.s; }; -FilterPlainLeftAnchored.prototype.fid = '|a'; +FilterPlainLeftAnchored.fid = FilterPlainLeftAnchored.prototype.fid = '|a'; FilterPlainLeftAnchored.prototype.toString = function() { return '|' + this.s; @@ -397,6 +423,10 @@ FilterPlainLeftAnchored.prototype.toSelfie = function() { return this.s; }; +FilterPlainLeftAnchored.compile = function(details) { + return details.f; +}; + FilterPlainLeftAnchored.fromSelfie = function(s) { return new FilterPlainLeftAnchored(s); }; @@ -413,7 +443,7 @@ FilterPlainLeftAnchoredHostname.prototype.match = function(url) { url.slice(0, this.s.length) === this.s; }; -FilterPlainLeftAnchoredHostname.prototype.fid = '|ah'; +FilterPlainLeftAnchoredHostname.fid = FilterPlainLeftAnchoredHostname.prototype.fid = '|ah'; FilterPlainLeftAnchoredHostname.prototype.toString = function() { return '|' + this.s + '$domain=' + this.hostname; @@ -424,6 +454,10 @@ FilterPlainLeftAnchoredHostname.prototype.toSelfie = function() { this.hostname; }; +FilterPlainLeftAnchoredHostname.compile = function(details, hostname) { + return details.f + '\t' + hostname; +}; + FilterPlainLeftAnchoredHostname.fromSelfie = function(s) { var pos = s.indexOf('\t'); return new FilterPlainLeftAnchoredHostname(s.slice(0, pos), s.slice(pos + 1)); @@ -439,7 +473,7 @@ FilterPlainRightAnchored.prototype.match = function(url) { return url.slice(-this.s.length) === this.s; }; -FilterPlainRightAnchored.prototype.fid = 'a|'; +FilterPlainRightAnchored.fid = FilterPlainRightAnchored.prototype.fid = 'a|'; FilterPlainRightAnchored.prototype.toString = function() { return this.s + '|'; @@ -449,6 +483,10 @@ FilterPlainRightAnchored.prototype.toSelfie = function() { return this.s; }; +FilterPlainRightAnchored.compile = function(details) { + return details.f; +}; + FilterPlainRightAnchored.fromSelfie = function(s) { return new FilterPlainRightAnchored(s); }; @@ -465,7 +503,7 @@ FilterPlainRightAnchoredHostname.prototype.match = function(url) { url.slice(-this.s.length) === this.s; }; -FilterPlainRightAnchoredHostname.prototype.fid = 'a|h'; +FilterPlainRightAnchoredHostname.fid = FilterPlainRightAnchoredHostname.prototype.fid = 'a|h'; FilterPlainRightAnchoredHostname.prototype.toString = function() { return this.s + '|$domain=' + this.hostname; @@ -476,6 +514,10 @@ FilterPlainRightAnchoredHostname.prototype.toSelfie = function() { this.hostname; }; +FilterPlainRightAnchoredHostname.compile = function(details, hostname) { + return details.f + '\t' + hostname; +}; + FilterPlainRightAnchoredHostname.fromSelfie = function(s) { var pos = s.indexOf('\t'); return new FilterPlainRightAnchoredHostname(s.slice(0, pos), s.slice(pos + 1)); @@ -500,7 +542,7 @@ FilterPlainHnAnchored.prototype.match = function(url, tokenBeg) { reURLPostHostnameAnchors.test(url.slice(pos + 3, tokenBeg)) === false; }; -FilterPlainHnAnchored.prototype.fid = 'h|a'; +FilterPlainHnAnchored.fid = FilterPlainHnAnchored.prototype.fid = 'h|a'; FilterPlainHnAnchored.prototype.toString = function() { return '||' + this.s; @@ -510,6 +552,10 @@ FilterPlainHnAnchored.prototype.toSelfie = function() { return this.s; }; +FilterPlainHnAnchored.compile = function(details) { + return details.f; +}; + FilterPlainHnAnchored.fromSelfie = function(s) { return new FilterPlainHnAnchored(s); }; @@ -535,7 +581,7 @@ FilterSingleWildcard.prototype.match = function(url, tokenBeg) { url.indexOf(this.rSegment, tokenBeg + this.lSegment.length) > 0; }; -FilterSingleWildcard.prototype.fid = '*'; +FilterSingleWildcard.fid = FilterSingleWildcard.prototype.fid = '*'; FilterSingleWildcard.prototype.toString = function() { return this.lSegment + '*' + this.rSegment; @@ -547,6 +593,14 @@ FilterSingleWildcard.prototype.toSelfie = function() { this.tokenBeg; }; +FilterSingleWildcard.compile = function(details) { + var s = details.f; + var pos = s.indexOf('*'); + return s.slice(0, pos) + '\t' + + s.slice(pos + 1) + '\t' + + details.tokenBeg; +}; + FilterSingleWildcard.fromSelfie = function(s) { var args = s.split('\t'); return new FilterSingleWildcard(args[0], args[1], atoi(args[2])); @@ -568,7 +622,7 @@ FilterSingleWildcardHostname.prototype.match = function(url, tokenBeg) { url.indexOf(this.rSegment, tokenBeg + this.lSegment.length) > 0; }; -FilterSingleWildcardHostname.prototype.fid = '*h'; +FilterSingleWildcardHostname.fid = FilterSingleWildcardHostname.prototype.fid = '*h'; FilterSingleWildcardHostname.prototype.toString = function() { return this.lSegment + '*' + this.rSegment + '$domain=' + this.hostname; @@ -581,6 +635,15 @@ FilterSingleWildcardHostname.prototype.toSelfie = function() { this.hostname; }; +FilterSingleWildcardHostname.compile = function(details, hostname) { + var s = details.f; + var pos = s.indexOf('*'); + return s.slice(0, pos) + '\t' + + s.slice(pos + 1) + '\t' + + details.tokenBeg + '\t' + + hostname; +}; + FilterSingleWildcardHostname.fromSelfie = function(s) { var args = s.split('\t'); return new FilterSingleWildcardHostname(args[0], args[1], atoi(args[2]), args[3]); @@ -598,7 +661,7 @@ FilterSingleWildcardPrefix0.prototype.match = function(url, tokenBeg) { url.indexOf(this.rSegment, tokenBeg + this.lSegment.length) > 0; }; -FilterSingleWildcardPrefix0.prototype.fid = '0*'; +FilterSingleWildcardPrefix0.fid = FilterSingleWildcardPrefix0.prototype.fid = '0*'; FilterSingleWildcardPrefix0.prototype.toString = function() { return this.lSegment + '*' + this.rSegment; @@ -609,6 +672,12 @@ FilterSingleWildcardPrefix0.prototype.toSelfie = function() { this.rSegment; }; +FilterSingleWildcardPrefix0.compile = function(details) { + var s = details.f; + var pos = s.indexOf('*'); + return s.slice(0, pos) + '\t' + s.slice(pos + 1); +}; + FilterSingleWildcardPrefix0.fromSelfie = function(s) { var pos = s.indexOf('\t'); return new FilterSingleWildcardPrefix0(s.slice(0, pos), s.slice(pos + 1)); @@ -628,7 +697,7 @@ FilterSingleWildcardPrefix0Hostname.prototype.match = function(url, tokenBeg) { url.indexOf(this.rSegment, tokenBeg + this.lSegment.length) > 0; }; -FilterSingleWildcardPrefix0Hostname.prototype.fid = '0*h'; +FilterSingleWildcardPrefix0Hostname.fid = FilterSingleWildcardPrefix0Hostname.prototype.fid = '0*h'; FilterSingleWildcardPrefix0Hostname.prototype.toString = function() { return this.lSegment + '*' + this.rSegment + '$domain=' + this.hostname; @@ -640,6 +709,14 @@ FilterSingleWildcardPrefix0Hostname.prototype.toSelfie = function() { this.hostname; }; +FilterSingleWildcardPrefix0Hostname.compile = function(details, hostname) { + var s = details.f; + var pos = s.indexOf('*'); + return s.slice(0, pos) + '\t' + + s.slice(pos + 1) + '\t' + + hostname; +}; + FilterSingleWildcardPrefix0Hostname.fromSelfie = function(s) { var args = s.split('\t'); return new FilterSingleWildcardPrefix0Hostname(args[0], args[1], args[2]); @@ -657,7 +734,7 @@ FilterSingleWildcardLeftAnchored.prototype.match = function(url) { url.indexOf(this.rSegment, this.lSegment.length) > 0; }; -FilterSingleWildcardLeftAnchored.prototype.fid = '|*'; +FilterSingleWildcardLeftAnchored.fid = FilterSingleWildcardLeftAnchored.prototype.fid = '|*'; FilterSingleWildcardLeftAnchored.prototype.toString = function() { return '|' + this.lSegment + '*' + this.rSegment; @@ -668,6 +745,13 @@ FilterSingleWildcardLeftAnchored.prototype.toSelfie = function() { this.rSegment; }; +FilterSingleWildcardLeftAnchored.compile = function(details) { + var s = details.f; + var pos = s.indexOf('*'); + return s.slice(0, pos) + '\t' + + s.slice(pos + 1) + '\t'; +}; + FilterSingleWildcardLeftAnchored.fromSelfie = function(s) { var pos = s.indexOf('\t'); return new FilterSingleWildcardLeftAnchored(s.slice(0, pos), s.slice(pos + 1)); @@ -687,7 +771,7 @@ FilterSingleWildcardLeftAnchoredHostname.prototype.match = function(url) { url.indexOf(this.rSegment, this.lSegment.length) > 0; }; -FilterSingleWildcardLeftAnchoredHostname.prototype.fid = '|*h'; +FilterSingleWildcardLeftAnchoredHostname.fid = FilterSingleWildcardLeftAnchoredHostname.prototype.fid = '|*h'; FilterSingleWildcardLeftAnchoredHostname.prototype.toString = function() { return '|' + this.lSegment + '*' + this.rSegment + '$domain=' + this.hostname; @@ -699,6 +783,14 @@ FilterSingleWildcardLeftAnchoredHostname.prototype.toSelfie = function() { this.hostname; }; +FilterSingleWildcardLeftAnchoredHostname.compile = function(details, hostname) { + var s = details.f; + var pos = s.indexOf('*'); + return s.slice(0, pos) + '\t' + + s.slice(pos + 1) + '\t' + + hostname; +}; + FilterSingleWildcardLeftAnchoredHostname.fromSelfie = function(s) { var args = s.split('\t'); return new FilterSingleWildcardLeftAnchoredHostname(args[0], args[1], args[2]); @@ -716,7 +808,7 @@ FilterSingleWildcardRightAnchored.prototype.match = function(url) { url.lastIndexOf(this.lSegment, url.length - this.rSegment.length - this.lSegment.length) >= 0; }; -FilterSingleWildcardRightAnchored.prototype.fid = '*|'; +FilterSingleWildcardRightAnchored.fid = FilterSingleWildcardRightAnchored.prototype.fid = '*|'; FilterSingleWildcardRightAnchored.prototype.toString = function() { return this.lSegment + '*' + this.rSegment + '|'; @@ -727,6 +819,13 @@ FilterSingleWildcardRightAnchored.prototype.toSelfie = function() { this.rSegment; }; +FilterSingleWildcardRightAnchored.compile = function(details) { + var s = details.f; + var pos = s.indexOf('*'); + return s.slice(0, pos) + '\t' + + s.slice(pos + 1) + '\t'; +}; + FilterSingleWildcardRightAnchored.fromSelfie = function(s) { var pos = s.indexOf('\t'); return new FilterSingleWildcardRightAnchored(s.slice(0, pos), s.slice(pos + 1)); @@ -746,7 +845,7 @@ FilterSingleWildcardRightAnchoredHostname.prototype.match = function(url) { url.lastIndexOf(this.lSegment, url.length - this.rSegment.length - this.lSegment.length) >= 0; }; -FilterSingleWildcardRightAnchoredHostname.prototype.fid = '*|h'; +FilterSingleWildcardRightAnchoredHostname.fid = FilterSingleWildcardRightAnchoredHostname.prototype.fid = '*|h'; FilterSingleWildcardRightAnchoredHostname.prototype.toString = function() { return this.lSegment + '*' + this.rSegment + '|$domain=' + this.hostname; @@ -758,6 +857,14 @@ FilterSingleWildcardRightAnchoredHostname.prototype.toSelfie = function() { this.hostname; }; +FilterSingleWildcardRightAnchoredHostname.compile = function(details, hostname) { + var s = details.f; + var pos = s.indexOf('*'); + return s.slice(0, pos) + '\t' + + s.slice(pos + 1) + '\t' + + hostname; +}; + FilterSingleWildcardRightAnchoredHostname.fromSelfie = function(s) { var args = s.split('\t'); return new FilterSingleWildcardRightAnchoredHostname(args[0], args[1], args[2]); @@ -781,15 +888,18 @@ FilterManyWildcards.prototype.match = function(url, tokenBeg) { return this.re.test(url.slice(tokenBeg - this.tokenBeg)); }; -FilterManyWildcards.prototype.fid = '*+'; +FilterManyWildcards.fid = FilterManyWildcards.prototype.fid = '*+'; FilterManyWildcards.prototype.toString = function() { return this.s; }; FilterManyWildcards.prototype.toSelfie = function() { - return this.s + '\t' + - this.tokenBeg; + return this.s + '\t' + this.tokenBeg; +}; + +FilterManyWildcards.compile = function(details) { + return details.f + '\t' + details.tokenBeg; }; FilterManyWildcards.fromSelfie = function(s) { @@ -811,7 +921,7 @@ FilterManyWildcardsHostname.prototype.match = function(url, tokenBeg) { this.re.test(url.slice(tokenBeg - this.tokenBeg)); }; -FilterManyWildcardsHostname.prototype.fid = '*+h'; +FilterManyWildcardsHostname.fid = FilterManyWildcardsHostname.prototype.fid = '*+h'; FilterManyWildcardsHostname.prototype.toString = function() { return this.s + '$domain=' + this.hostname; @@ -823,6 +933,12 @@ FilterManyWildcardsHostname.prototype.toSelfie = function() { this.hostname; }; +FilterManyWildcardsHostname.compile = function(details, hostname) { + return details.f + '\t' + + details.tokenBeg + '\t' + + hostname; +}; + FilterManyWildcardsHostname.fromSelfie = function(s) { var args = s.split('\t'); return new FilterManyWildcardsHostname(args[0], atoi(args[1]), args[2]); @@ -840,7 +956,7 @@ FilterRegex.prototype.match = function(url) { return this.re.test(url); }; -FilterRegex.prototype.fid = '//'; +FilterRegex.fid = FilterRegex.prototype.fid = '//'; FilterRegex.prototype.toString = function() { return '/' + this.re.source + '/'; @@ -850,6 +966,10 @@ FilterRegex.prototype.toSelfie = function() { return this.re.source; }; +FilterRegex.compile = function(details) { + return details.f; +}; + FilterRegex.fromSelfie = function(s) { return new FilterRegex(s); }; @@ -867,15 +987,18 @@ FilterRegexHostname.prototype.match = function(url) { this.re.test(url); }; -FilterRegexHostname.prototype.fid = '//h'; +FilterRegexHostname.fid = FilterRegexHostname.prototype.fid = '//h'; FilterRegexHostname.prototype.toString = function() { return '/' + this.re.source + '/$domain=' + this.hostname; }; FilterRegexHostname.prototype.toSelfie = function() { - return this.re.source + '\t' + - this.hostname; + return this.re.source + '\t' + this.hostname; +}; + +FilterRegexHostname.compile = function(details, hostname) { + return details.f + '\t' + hostname; }; FilterRegexHostname.fromSelfie = function(s) { @@ -981,12 +1104,12 @@ FilterHostnameDict.prototype.add = function(hn) { if ( typeof bucket === 'string' ) { bucket = this.dict[key] = this.meltBucket(hn.len, bucket); } - if ( bucket[hn] === undefined ) { - bucket[hn] = true; - this.count += 1; - return true; + if ( bucket.hasOwnProperty(hn) ) { + return false; } - return false; + bucket[hn] = true; + this.count += 1; + return true; }; FilterHostnameDict.prototype.freeze = function() { @@ -1041,7 +1164,7 @@ FilterHostnameDict.prototype.matchesExactly = function(hn) { return false; }; -FilterHostnameDict.prototype.match = function(hn) { +FilterHostnameDict.prototype.match = function() { // TODO: mind IP addresses var pos, @@ -1058,7 +1181,7 @@ FilterHostnameDict.prototype.match = function(hn) { return this; }; -FilterHostnameDict.prototype.fid = '{h}'; +FilterHostnameDict.fid = FilterHostnameDict.prototype.fid = '{h}'; FilterHostnameDict.prototype.toString = function() { return this.h; @@ -1186,85 +1309,81 @@ FilterBucket.fromSelfie = function() { /******************************************************************************/ -var makeFilter = function(details) { - var s = details.f; +var getFilterClass = function(details) { if ( details.isRegex ) { - return new FilterRegex(s); + return FilterRegex; } + var s = details.f; var wcOffset = s.indexOf('*'); if ( wcOffset !== -1 ) { if ( s.indexOf('*', wcOffset + 1) !== -1 ) { - return details.anchor === 0 ? new FilterManyWildcards(s, details.tokenBeg) : null; + return details.anchor === 0 ? FilterManyWildcards : null; } - var lSegment = s.slice(0, wcOffset); - var rSegment = s.slice(wcOffset + 1); if ( details.anchor < 0 ) { - return new FilterSingleWildcardLeftAnchored(lSegment, rSegment); + return FilterSingleWildcardLeftAnchored; } if ( details.anchor > 0 ) { - return new FilterSingleWildcardRightAnchored(lSegment, rSegment); + return FilterSingleWildcardRightAnchored; } if ( details.tokenBeg === 0 ) { - return new FilterSingleWildcardPrefix0(lSegment, rSegment); + return FilterSingleWildcardPrefix0; } - return new FilterSingleWildcard(lSegment, rSegment, details.tokenBeg); + return FilterSingleWildcard; } if ( details.anchor < 0 ) { - return new FilterPlainLeftAnchored(s); + return FilterPlainLeftAnchored; } if ( details.anchor > 0 ) { - return new FilterPlainRightAnchored(s); + return FilterPlainRightAnchored; } if ( details.hostnameAnchored ) { - return new FilterPlainHnAnchored(s); + return FilterPlainHnAnchored; } if ( details.tokenBeg === 0 ) { - return new FilterPlainPrefix0(s); + return FilterPlainPrefix0; } if ( details.tokenBeg === 1 ) { - return new FilterPlainPrefix1(s); + return FilterPlainPrefix1; } - return new FilterPlain(s, details.tokenBeg); + return FilterPlain; }; /******************************************************************************/ -var makeHostnameFilter = function(details, hostname) { - var s = details.f; +var getHostnameBasedFilterClass = function(details) { if ( details.isRegex ) { - return new FilterRegexHostname(s, hostname); + return FilterRegexHostname; } + var s = details.f; var wcOffset = s.indexOf('*'); if ( wcOffset !== -1 ) { if ( s.indexOf('*', wcOffset + 1) !== -1 ) { - return details.anchor === 0 ? new FilterManyWildcardsHostname(s, details.tokenBeg, hostname) : null; + return details.anchor === 0 ? FilterManyWildcardsHostname : null; } - var lSegment = s.slice(0, wcOffset); - var rSegment = s.slice(wcOffset + 1); if ( details.anchor < 0 ) { - return new FilterSingleWildcardLeftAnchoredHostname(lSegment, rSegment, hostname); + return FilterSingleWildcardLeftAnchoredHostname; } if ( details.anchor > 0 ) { - return new FilterSingleWildcardRightAnchoredHostname(lSegment, rSegment, hostname); + return FilterSingleWildcardRightAnchoredHostname; } if ( details.tokenBeg === 0 ) { - return new FilterSingleWildcardPrefix0Hostname(lSegment, rSegment, hostname); + return FilterSingleWildcardPrefix0Hostname; } - return new FilterSingleWildcardHostname(lSegment, rSegment, details.tokenBeg, hostname); + return FilterSingleWildcardHostname; } if ( details.anchor < 0 ) { - return new FilterPlainLeftAnchoredHostname(s, hostname); + return FilterPlainLeftAnchoredHostname; } if ( details.anchor > 0 ) { - return new FilterPlainRightAnchoredHostname(s, hostname); + return FilterPlainRightAnchoredHostname; } if ( details.tokenBeg === 0 ) { - return new FilterPlainPrefix0Hostname(s, hostname); + return FilterPlainPrefix0Hostname; } if ( details.tokenBeg === 1 ) { - return new FilterPlainPrefix1Hostname(s, hostname); + return FilterPlainPrefix1Hostname; } - return new FilterPlainHostname(s, details.tokenBeg, hostname); + return FilterPlainHostname; }; /******************************************************************************/ @@ -1615,8 +1734,8 @@ FilterContainer.prototype.reset = function() { this.allowFilterCount = 0; this.blockFilterCount = 0; this.duplicateCount = 0; + this.duplicateBuster = {}; this.categories = Object.create(null); - this.duplicates = Object.create(null); this.filterParser.reset(); }; @@ -1624,6 +1743,8 @@ FilterContainer.prototype.reset = function() { FilterContainer.prototype.freeze = function() { histogram('allFilters', this.categories); + this.duplicateBuster = {}; + var categories = this.categories; var bucket; for ( var k in categories ) { @@ -1632,13 +1753,43 @@ FilterContainer.prototype.freeze = function() { bucket.freeze(); } } - this.duplicates = Object.create(null); + this.filterParser.reset(); this.frozen = true; }; /******************************************************************************/ +FilterContainer.prototype.factories = { + '[]': FilterBucket, + 'a': FilterPlain, + 'ah': FilterPlainHostname, + '0a': FilterPlainPrefix0, + '0ah': FilterPlainPrefix0Hostname, + '1a': FilterPlainPrefix1, + '1ah': FilterPlainPrefix1Hostname, + '|a': FilterPlainLeftAnchored, + '|ah': FilterPlainLeftAnchoredHostname, + 'a|': FilterPlainRightAnchored, + 'a|h': FilterPlainRightAnchoredHostname, + 'h|a': FilterPlainHnAnchored, + '*': FilterSingleWildcard, + '*h': FilterSingleWildcardHostname, + '0*': FilterSingleWildcardPrefix0, + '0*h': FilterSingleWildcardPrefix0Hostname, + '|*': FilterSingleWildcardLeftAnchored, + '|*h': FilterSingleWildcardLeftAnchoredHostname, + '*|': FilterSingleWildcardRightAnchored, + '*|h': FilterSingleWildcardRightAnchoredHostname, + '*+': FilterManyWildcards, + '*+h': FilterManyWildcardsHostname, + '//': FilterRegex, + '//h': FilterRegexHostname, + '{h}': FilterHostnameDict +}; + +/******************************************************************************/ + FilterContainer.prototype.toSelfie = function() { var categoryToSelfie = function(dict) { var selfie = []; @@ -1697,34 +1848,6 @@ FilterContainer.prototype.fromSelfie = function(selfie) { this.blockFilterCount = selfie.blockFilterCount; this.duplicateCount = selfie.duplicateCount; - var factories = { - '[]': FilterBucket, - 'a': FilterPlain, - 'ah': FilterPlainHostname, - '0a': FilterPlainPrefix0, - '0ah': FilterPlainPrefix0Hostname, - '1a': FilterPlainPrefix1, - '1ah': FilterPlainPrefix1Hostname, - '|a': FilterPlainLeftAnchored, - '|ah': FilterPlainLeftAnchoredHostname, - 'a|': FilterPlainRightAnchored, - 'a|h': FilterPlainRightAnchoredHostname, - 'h|a': FilterPlainHnAnchored, - '*': FilterSingleWildcard, - '*h': FilterSingleWildcardHostname, - '0*': FilterSingleWildcardPrefix0, - '0*h': FilterSingleWildcardPrefix0Hostname, - '|*': FilterSingleWildcardLeftAnchored, - '|*h': FilterSingleWildcardLeftAnchoredHostname, - '*|': FilterSingleWildcardRightAnchored, - '*|h': FilterSingleWildcardRightAnchoredHostname, - '*+': FilterManyWildcards, - '*+h': FilterManyWildcardsHostname, - '//': FilterRegex, - '//h': FilterRegexHostname, - '{h}': FilterHostnameDict - }; - var catKey, tokenKey; var dict = this.categories, subdict; var bucket = null; @@ -1752,7 +1875,7 @@ FilterContainer.prototype.fromSelfie = function(selfie) { bucket = null; continue; } - factory = factories[what]; + factory = this.factories[what]; if ( bucket === null ) { bucket = subdict[tokenKey] = factory.fromSelfie(line.slice(pos + 1)); continue; @@ -1766,12 +1889,12 @@ FilterContainer.prototype.fromSelfie = function(selfie) { /******************************************************************************/ FilterContainer.prototype.makeCategoryKey = function(category) { - return String.fromCharCode(category); + return category.toString(16); }; /******************************************************************************/ -FilterContainer.prototype.add = function(raw) { +FilterContainer.prototype.compile = function(raw, out) { // ORDER OF TESTS IS IMPORTANT! // Ignore empty lines @@ -1795,40 +1918,22 @@ FilterContainer.prototype.add = function(raw) { // Ignore filters with unsupported options if ( parsed.unsupported ) { - this.rejectedCount += 1; //console.log('static-net-filtering.js > FilterContainer.add(): unsupported filter "%s"', raw); return false; } - this.processedFilterCount += 1; - this.acceptedCount += 1; - // Pure hostnames, use more efficient liquid dict // https://github.com/gorhill/uBlock/issues/665 // Create a dict keyed on request type etc. - if ( parsed.hostnamePure && this.addHostnameOnlyFilter(parsed) ) { + if ( parsed.hostnamePure && this.compileHostnameOnlyFilter(parsed, out) ) { return true; } - if ( this.duplicates[s] ) { - //console.log('static-net-filtering.js > FilterContainer.add(): duplicate filter "%s"', raw); - this.duplicateCount++; - return false; - } - if ( this.frozen === false ) { - this.duplicates[s] = true; - } - - var r = this.addFilter(parsed); + var r = this.compileFilter(parsed, out); if ( r === false ) { return false; } - if ( parsed.action ) { - this.allowFilterCount += 1; - } else { - this.blockFilterCount += 1; - } return true; }; @@ -1836,55 +1941,37 @@ FilterContainer.prototype.add = function(raw) { // Using fast/compact dictionary when filter is a (or portion of) pure hostname. -FilterContainer.prototype.addHostnameOnlyFilter = function(parsed) { +FilterContainer.prototype.compileHostnameOnlyFilter = function(parsed, out) { // Can't fit the filter in a pure hostname dictionary. if ( parsed.hostnames.length !== 0 || parsed.notHostnames.length !== 0 ) { - return false; + return; } - var isNewFilter = false; var party = AnyParty; if ( parsed.firstParty !== parsed.thirdParty ) { party = parsed.firstParty ? FirstParty : ThirdParty; } var keyShard = parsed.action | parsed.important | party; - var key, bucket; var type = parsed.types >>> 1 || 1; // bit 0 is unused; also, default to AnyType var bitOffset = 1; while ( type !== 0 ) { if ( type & 1 ) { - key = this.makeCategoryKey(keyShard | (bitOffset << 4)); - bucket = this.categories[key]; - if ( bucket === undefined ) { - bucket = this.categories[key] = Object.create(null); - } - if ( bucket['.'] === undefined ) { - bucket['.'] = new FilterHostnameDict(); - } - if ( bucket['.'].add(parsed.f) ) { - isNewFilter = true; - } + out.push( + 'n\v' + + this.makeCategoryKey(keyShard | (bitOffset << 4)) + '\v' + + '.\v' + + parsed.f + ); } bitOffset += 1; type >>>= 1; } - // https://github.com/gorhill/uBlock/issues/719 - // Count whole filter, not its decomposed versions - if ( isNewFilter ) { - if ( parsed.action ) { - this.allowFilterCount += 1; - } else { - this.blockFilterCount += 1; - } - } else { - this.duplicateCount += 1; - } return true; }; /******************************************************************************/ -FilterContainer.prototype.addFilter = function(parsed) { +FilterContainer.prototype.compileFilter = function(parsed, out) { parsed.makeToken(); if ( parsed.token === '' ) { console.error('static-net-filtering.js > FilterContainer.addFilter("%s"): can\'t tokenize', parsed.f); @@ -1896,28 +1983,28 @@ FilterContainer.prototype.addFilter = function(parsed) { party = parsed.firstParty ? FirstParty : ThirdParty; } - var filter; + var filterClass; var i = parsed.hostnames.length; var j = parsed.notHostnames.length; // Applies to all domains without exceptions if ( i === 0 && j === 0 ) { - filter = makeFilter(parsed); - if ( !filter ) { + filterClass = getFilterClass(parsed); + if ( filterClass === null ) { return false; } - this.addFilterEntry(filter, parsed, party); + this.compileToAtomicFilter(filterClass, parsed, party, out); return true; } // Applies to specific domains if ( i !== 0 ) { while ( i-- ) { - filter = makeHostnameFilter(parsed, parsed.hostnames[i]); - if ( !filter ) { + filterClass = getHostnameBasedFilterClass(parsed); + if ( filterClass === null ) { return false; } - this.addFilterEntry(filter, parsed, party); + this.compileToAtomicFilter(filterClass, parsed, party, out, parsed.hostnames[i]); } } // No exceptions @@ -1930,13 +2017,13 @@ FilterContainer.prototype.addFilter = function(parsed) { // Example: // - ||adm.fwmrm.net/p/msnbc_live/$object-subrequest,third-party,domain=~msnbc.msn.com|~www.nbcnews.com if ( i === 0 ) { - filter = makeFilter(parsed); - if ( !filter ) { + filterClass = getFilterClass(parsed); + if ( filterClass === null ) { return false; } // https://github.com/gorhill/uBlock/issues/251 // Apply third-party option if it is present - this.addFilterEntry(filter, parsed, party); + this.compileToAtomicFilter(filterClass, parsed, party, out); } // Cases: @@ -1948,8 +2035,8 @@ FilterContainer.prototype.addFilter = function(parsed) { // Reverse purpose of filter parsed.action ^= ToggleAction; while ( j-- ) { - filter = makeHostnameFilter(parsed, parsed.notHostnames[j]); - if ( !filter ) { + filterClass = getHostnameBasedFilterClass(parsed); + if ( filterClass === null ) { return false; } // https://github.com/gorhill/uBlock/issues/191#issuecomment-53654024 @@ -1958,20 +2045,26 @@ FilterContainer.prototype.addFilter = function(parsed) { if ( parsed.action === BlockAction ) { parsed.important = Important; } - this.addFilterEntry(filter, parsed, party); + this.compileToAtomicFilter(filterClass, parsed, party, out, parsed.notHostnames[j]); } return true; }; /******************************************************************************/ -FilterContainer.prototype.addFilterEntry = function(filter, parsed, party) { +FilterContainer.prototype.compileToAtomicFilter = function(filterClass, parsed, party, out, hostname) { var bits = parsed.action | parsed.important | party; var type = parsed.types >>> 1 || 1; // bit 0 is unused; also, default to AnyType var bitOffset = 1; while ( type !== 0 ) { if ( type & 1 ) { - this.addToCategory(bits | (bitOffset << 4), parsed.token, filter); + out.push( + 'n\v' + + this.makeCategoryKey(bits | (bitOffset << 4)) + '\v' + + parsed.token + '\v' + + filterClass.fid + '\v' + + filterClass.compile(parsed, hostname) + ); } bitOffset += 1; type >>>= 1; @@ -1980,22 +2073,60 @@ FilterContainer.prototype.addFilterEntry = function(filter, parsed, party) { /******************************************************************************/ -FilterContainer.prototype.addToCategory = function(category, tokenKey, filter) { - var categoryKey = this.makeCategoryKey(category); - var categoryBucket = this.categories[categoryKey]; - if ( !categoryBucket ) { - categoryBucket = this.categories[categoryKey] = Object.create(null); +FilterContainer.prototype.fromCompiledContent = function(text, lineBeg) { + var lineEnd; + var textEnd = text.length; + var line, fields, bucket, entry, factory, filter; + + while ( lineBeg < textEnd ) { + if ( text.charAt(lineBeg) !== 'n' ) { + return lineBeg; + } + lineEnd = text.indexOf('\n', lineBeg); + if ( lineEnd === -1 ) { + lineEnd = textEnd; + } + line = text.slice(lineBeg + 2, lineEnd); + fields = line.split('\v'); + lineBeg = lineEnd + 1; + + this.acceptedCount += 1; + + bucket = this.categories[fields[0]]; + if ( bucket === undefined ) { + bucket = this.categories[fields[0]] = Object.create(null); + } + entry = bucket[fields[1]]; + + if ( fields[1] === '.' ) { + if ( entry === undefined ) { + entry = bucket['.'] = new FilterHostnameDict(); + } + if ( entry.add(fields[2]) === false ) { + this.duplicateCount += 1; + } + continue; + } + + if ( this.duplicateBuster.hasOwnProperty(line) ) { + this.duplicateCount += 1; + continue; + } + this.duplicateBuster[line] = true; + + factory = this.factories[fields[2]]; + filter = factory.fromSelfie(fields[3]); + if ( entry === undefined ) { + bucket[fields[1]] = filter; + continue; + } + if ( entry.fid === '[]' ) { + entry.add(filter); + continue; + } + bucket[fields[1]] = new FilterBucket(entry, filter); } - var filterEntry = categoryBucket[tokenKey]; - if ( filterEntry === undefined ) { - categoryBucket[tokenKey] = filter; - return; - } - if ( filterEntry.fid === '[]' ) { - filterEntry.add(filter); - return; - } - categoryBucket[tokenKey] = new FilterBucket(filterEntry, filter); + return textEnd; }; /******************************************************************************/ @@ -2275,7 +2406,7 @@ FilterContainer.prototype.matchString = function(context) { /******************************************************************************/ FilterContainer.prototype.getFilterCount = function() { - return this.blockFilterCount + this.allowFilterCount; + return this.acceptedCount - this.duplicateCount; }; /******************************************************************************/ diff --git a/src/js/storage.js b/src/js/storage.js index 8c60582b2..b4e8a6911 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -19,7 +19,7 @@ Home: https://github.com/gorhill/uBlock */ -/* global µBlock, vAPI, punycode, publicSuffixList */ +/* global YaMD5, µBlock, vAPI, punycode, publicSuffixList */ 'use strict'; @@ -53,6 +53,21 @@ /******************************************************************************/ +µBlock.saveSystemSettings = function() { + vAPI.storage.set(this.systemSettings, this.noopFunc); +}; + +/******************************************************************************/ + +µBlock.loadSystemSettings = function(callback) { + vAPI.storage.get({ + compiledMagic: '', + selfieMagic: '' + }, callback); +}; + +/******************************************************************************/ + // Save local settings regularly. Not critical. µBlock.asyncJobs.add( @@ -133,17 +148,30 @@ /******************************************************************************/ µBlock.appendUserFilters = function(content) { + if ( content.length === 0 ) { + return; + } + var µb = this; + var onCompiledListLoaded = function(details) { + var snfe = µb.staticNetFilteringEngine; + var cfe = µb.cosmeticFilteringEngine; + var acceptedCount = snfe.acceptedCount + cfe.acceptedCount; + var duplicateCount = snfe.duplicateCount + cfe.duplicateCount; + µb.applyCompiledFilters(details.content); + var entry = µb.remoteBlacklists[µb.userFiltersPath]; + entry.entryCount = snfe.acceptedCount + cfe.acceptedCount - acceptedCount; + entry.entryUsedCount = entry.entryCount - snfe.duplicateCount - cfe.duplicateCount + duplicateCount; + µb.staticNetFilteringEngine.freeze(); + µb.cosmeticFilteringEngine.freeze(); + }; + var onSaved = function(details) { if ( details.error ) { return; } - µb.mergeFilterText(content); - µb.staticNetFilteringEngine.freeze(); - µb.cosmeticFilteringEngine.freeze(); - µb.destroySelfie(); - µb.toSelfieAsync(); + µb.getCompiledFilterList(µb.userFiltersPath, onCompiledListLoaded); }; var onLoaded = function(details) { @@ -153,12 +181,10 @@ if ( details.content.indexOf(content.trim()) !== -1 ) { return; } - µb.saveUserFilters(details.content + '\n' + content, onSaved); + µb.saveUserFilters(details.content.trim() + '\n' + content.trim(), onSaved); }; - if ( content.length > 0 ) { - this.loadUserFilters(onLoaded); - } + this.loadUserFilters(onLoaded); }; /******************************************************************************/ @@ -260,111 +286,152 @@ /******************************************************************************/ +µBlock.createShortUniqueId = function(path) { + var md5 = YaMD5.hashStr(path); + return md5.slice(0, 4) + md5.slice(-4); +}; + +µBlock.createShortUniqueId.idLength = 8; + +/******************************************************************************/ + µBlock.loadFilterLists = function(callback) { + + //quickProfiler.start('µBlock.loadFilterLists()'); + var µb = this; - var filterlistCount; + var filterlistsCount = 0; if ( typeof callback !== 'function' ) { callback = this.noopFunc; } - var loadBlacklistsEnd = function() { + var onDone = function() { µb.staticNetFilteringEngine.freeze(); µb.cosmeticFilteringEngine.freeze(); vAPI.storage.set({ 'remoteBlacklists': µb.remoteBlacklists }); - vAPI.messaging.broadcast({ what: 'loadUbiquitousBlacklistCompleted' }); - µb.toSelfieAsync(); + vAPI.messaging.broadcast({ what: 'allFilterListsReloaded' }); callback(); + + µb.toSelfieAsync(); + + //quickProfiler.stop(0); }; - var mergeBlacklist = function(details) { - µb.mergeFilterList(details); - filterlistCount -= 1; - if ( filterlistCount === 0 ) { - loadBlacklistsEnd(); + var applyCompiledFilters = function(path, compiled) { + var snfe = µb.staticNetFilteringEngine; + var cfe = µb.cosmeticFilteringEngine; + var acceptedCount = snfe.acceptedCount + cfe.acceptedCount; + var duplicateCount = snfe.duplicateCount + cfe.duplicateCount; + µb.applyCompiledFilters(compiled); + if ( µb.remoteBlacklists.hasOwnProperty(path) ) { + var entry = µb.remoteBlacklists[path]; + entry.entryCount = snfe.acceptedCount + cfe.acceptedCount - acceptedCount; + entry.entryUsedCount = entry.entryCount - snfe.duplicateCount - cfe.duplicateCount + duplicateCount; } }; - var loadBlacklistsStart = function(lists) { - µb.remoteBlacklists = lists; - µb.staticNetFilteringEngine.reset(); - µb.cosmeticFilteringEngine.reset(); - µb.destroySelfie(); - var locations = Object.keys(lists); - filterlistCount = locations.length; + var onCompiledListLoaded = function(details) { + applyCompiledFilters(details.path, details.content); + filterlistsCount -= 1; + if ( filterlistsCount === 0 ) { + onDone(); + } + }; - // Load all filter lists which are not disabled - var location; - while ( location = locations.pop() ) { - // rhill 2013-12-09: - // Ignore list if disabled - // https://github.com/gorhill/httpswitchboard/issues/78 - if ( lists[location].off ) { - filterlistCount -= 1; + var onFilterListsReady = function(lists) { + µb.remoteBlacklists = lists; + + µb.cosmeticFilteringEngine.reset(); + µb.staticNetFilteringEngine.reset(); + µb.destroySelfie(); + + // 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 + // content. + var toLoad = []; + for ( var path in lists ) { + if ( lists.hasOwnProperty(path) === false ) { continue; } - µb.assets.get(location, mergeBlacklist); + if ( lists[path].off ) { + continue; + } + toLoad.push(path); } - // https://github.com/gorhill/uBlock/issues/695 - // It may happen not a single filter list is selected - if ( filterlistCount === 0 ) { - loadBlacklistsEnd(); + filterlistsCount = toLoad.length; + if ( filterlistsCount === 0 ) { + onDone(); + return; + } + + var i = toLoad.length; + while ( i-- ) { + µb.getCompiledFilterList(toLoad[i], onCompiledListLoaded); } }; - this.getAvailableLists(loadBlacklistsStart); + this.getAvailableLists(onFilterListsReady); }; /******************************************************************************/ -µBlock.mergeFilterList = function(details) { - // console.log('µBlock > mergeFilterList from "%s": "%s..."', details.path, details.content.slice(0, 40)); +µBlock.getCompiledFilterListPath = function(path) { + return 'cache://compiled-filter-list:' + this.createShortUniqueId(path); +}; - var staticNetFilteringEngine = this.staticNetFilteringEngine; - var cosmeticFilteringEngine = this.cosmeticFilteringEngine; - var duplicateCount = staticNetFilteringEngine.duplicateCount + cosmeticFilteringEngine.duplicateCount; - var acceptedCount = staticNetFilteringEngine.acceptedCount + cosmeticFilteringEngine.acceptedCount; +/******************************************************************************/ - this.mergeFilterText(details.content); +µBlock.getCompiledFilterList = function(path, callback) { + var compiledPath = this.getCompiledFilterListPath(path); + var µb = this; - // For convenience, store the number of entries for this - // blacklist, user might be happy to know this information. - duplicateCount = staticNetFilteringEngine.duplicateCount + cosmeticFilteringEngine.duplicateCount - duplicateCount; - acceptedCount = staticNetFilteringEngine.acceptedCount + cosmeticFilteringEngine.acceptedCount - acceptedCount; - - var filterListMeta = this.remoteBlacklists[details.path]; - - filterListMeta.entryCount = acceptedCount; - filterListMeta.entryUsedCount = acceptedCount - duplicateCount; - - // Try to extract a human-friendly name (works only for - // ABP-compatible filter lists) - if ( filterListMeta.title === '' ) { - var matches = details.content.slice(0, 1024).match(/(?:^|\n)!\s*Title:([^\n]+)/i); - if ( matches !== null ) { - filterListMeta.title = matches[1].trim(); + var onRawListLoaded = function(details) { + if ( details.content !== '' ) { + //console.debug('µBlock.getCompiledFilterList/onRawListLoaded: compiling "%s"', path); + details.content = µb.compileFilters(details.content); + µb.assets.put(compiledPath, details.content); } - } + callback(details); + }; + + var onCompiledListLoaded = function(details) { + if ( details.content === '' ) { + //console.debug('µBlock.getCompiledFilterList/onCompiledListLoaded: no compiled version for "%s"', path); + µb.assets.get(path, onRawListLoaded); + return; + } + //console.debug('µBlock.getCompiledFilterList/onCompiledListLoaded: using compiled version for "%s"', path); + details.path = path; + callback(details); + }; + + this.assets.get(compiledPath, onCompiledListLoaded); }; /******************************************************************************/ -µBlock.mergeFilterText = function(rawText) { +µBlock.purgeCompiledFilterList = function(path) { + this.assets.purge(this.getCompiledFilterListPath(path)); +}; + +/******************************************************************************/ + +µBlock.compileFilters = function(rawText) { var rawEnd = rawText.length; + var compiledFilters = []; // Useful references: // https://adblockplus.org/en/filter-cheatsheet // https://adblockplus.org/en/filters var staticNetFilteringEngine = this.staticNetFilteringEngine; var cosmeticFilteringEngine = this.cosmeticFilteringEngine; - var parseCosmeticFilters = this.userSettings.parseAllABPHideFilters; - - var reIsCosmeticFilter = /#[@#]/; var reIsWhitespaceChar = /\s/; var reMaybeLocalIp = /^[\d:f]/; var reIsLocalhostRedirect = /\s+(?:broadcasthost|local|localhost|localhost\.localdomain)(?=\s|$)/; var reLocalIp = /^(?:0\.0\.0\.0|127\.0\.0\.1|::1|fe80::1%lo0)/; - //var reAsciiSegment = /^[\x21-\x7e]+$/; + var lineBeg = 0, lineEnd, currentLineBeg; var line, lineRaw, c, pos; @@ -396,11 +463,7 @@ // Parse or skip cosmetic filters // All cosmetic filters are caught here - if ( parseCosmeticFilters ) { - if ( cosmeticFilteringEngine.add(line) ) { - continue; - } - } else if ( reIsCosmeticFilter.test(line) ) { + if ( cosmeticFilteringEngine.compile(line, compiledFilters) ) { continue; } @@ -440,41 +503,46 @@ continue; } - // The filter is whatever sequence of printable ascii character without - // whitespaces - //matches = reAsciiSegment.exec(line); - //if ( matches === null ) { - // console.debug('storage.js > µBlock.mergeFilterList(): skipping "%s"', lineRaw); - // continue; - //} + //staticNetFilteringEngine.add(line); + staticNetFilteringEngine.compile(line, compiledFilters); + } - // Bypass anomalies - // For example, when a filter contains whitespace characters, or - // whatever else outside the range of printable ascii characters. - //if ( matches[0] !== line ) { - // console.error('"%s" !== "%s"', matches[0], line); - // continue; - //} + return compiledFilters.join('\n'); +}; - staticNetFilteringEngine.add(line); +/******************************************************************************/ + +µBlock.applyCompiledFilters = function(rawText) { + var skipCosmetic = !this.userSettings.parseAllABPHideFilters; + var staticNetFilteringEngine = this.staticNetFilteringEngine; + var cosmeticFilteringEngine = this.cosmeticFilteringEngine; + var lineBeg = 0; + var rawEnd = rawText.length; + while ( lineBeg < rawEnd ) { + lineBeg = cosmeticFilteringEngine.fromCompiledContent(rawText, lineBeg, skipCosmetic); + lineBeg = staticNetFilteringEngine.fromCompiledContent(rawText, lineBeg); } }; /******************************************************************************/ -// `switches` contains the preset blacklists for which the switch must be -// revisited. +// `switches` contains the filter lists for which the switch must be revisited. -µBlock.reloadPresetBlacklists = function(switches, update) { - var µb = µBlock; +µBlock.reloadFilterLists = function(switches, update) { + var µb = this; var onFilterListsReady = function() { µb.loadUpdatableAssets({ update: update, psl: update }); }; - // Toggle switches, if any - if ( switches !== undefined ) { - var filterLists = this.remoteBlacklists; + var onPurgeDone = function() { + // Toggle switches, if any + if ( switches === undefined ) { + onFilterListsReady(); + return; + } + + var filterLists = µb.remoteBlacklists; var i = switches.length; while ( i-- ) { if ( filterLists.hasOwnProperty(switches[i].location) === false ) { @@ -484,26 +552,76 @@ } // Save switch states vAPI.storage.set({ 'remoteBlacklists': filterLists }, onFilterListsReady); - } else { - onFilterListsReady(); + }; + + // If we must update, we need to purge the compiled versions of + // obsolete assets. + if ( update !== true ) { + onPurgeDone(); + return; } + + var onMetadataReady = function(metadata) { + var filterLists = µb.remoteBlacklists; + var entry; + // Purge obsolete filter lists + for ( var path in filterLists ) { + if ( filterLists.hasOwnProperty(path) === false ) { + continue; + } + if ( metadata.hasOwnProperty(path) === false ) { + continue; + } + entry = metadata[path]; + if ( entry.repoObsolete !== true && entry.cacheObsolete !== true ) { + continue; + } + µb.purgeCompiledFilterList(path); + } + // Purge obsolete PSL + if ( metadata.hasOwnProperty(µb.pslPath) === false ) { + entry = metadata[µb.pslPath]; + if ( entry.repoObsolete === true ) { + µb.assets.purge('cache://compiled-publicsuffixlist'); + } + } + onPurgeDone(); + }; + + this.assets.metadata(onMetadataReady); }; /******************************************************************************/ µBlock.loadPublicSuffixList = function(callback) { + var µb = this; + var path = µb.pslPath; + var compiledPath = 'cache://compiled-publicsuffixlist'; + if ( typeof callback !== 'function' ) { callback = this.noopFunc; } - var applyPublicSuffixList = function(details) { - // TODO: Not getting proper suffix list is a bit serious, I think - // the extension should be force-restarted if it occurs.. - if ( !details.error ) { + var onRawListLoaded = function(details) { + if ( details.content !== '' ) { + //console.debug('µBlock.loadPublicSuffixList/onRawListLoaded: compiling "%s"', path); publicSuffixList.parse(details.content, punycode.toASCII); + µb.assets.put(compiledPath, JSON.stringify(publicSuffixList.toSelfie())); } callback(); }; - this.assets.get(this.pslPath, applyPublicSuffixList); + + var onCompiledListLoaded = function(details) { + if ( details.content === '' ) { + //console.debug('µBlock.loadPublicSuffixList/onCompiledListLoaded: no compiled version for "%s"', path); + µb.assets.get(path, onRawListLoaded); + return; + } + //console.debug('µBlock.loadPublicSuffixList/onCompiledListLoaded: using compiled version for "%s"', path); + publicSuffixList.fromSelfie(JSON.parse(details.content)); + callback(); + }; + + this.assets.get(compiledPath, onCompiledListLoaded); }; /******************************************************************************/ @@ -540,7 +658,7 @@ µBlock.toSelfie = function() { var selfie = { - magic: this.selfieMagic, + magic: this.systemSettings.selfieMagic, publicSuffixList: publicSuffixList.toSelfie(), filterLists: this.remoteBlacklists, staticNetFilteringEngine: this.staticNetFilteringEngine.toSelfie(), @@ -578,7 +696,7 @@ var onSelfieReady = function(store) { var selfie = store.selfie; - if ( typeof selfie !== 'object' || selfie.magic !== µb.selfieMagic ) { + if ( typeof selfie !== 'object' || selfie.magic !== µb.systemSettings.selfieMagic ) { callback(false); return; } @@ -586,7 +704,7 @@ callback(false); return; } - // console.log('µBlock.fromSelfie> selfie looks good'); + //console.log('µBlock.fromSelfie> selfie looks good'); µb.remoteBlacklists = selfie.filterLists; µb.staticNetFilteringEngine.fromSelfie(selfie.staticNetFilteringEngine); µb.cosmeticFilteringEngine.fromSelfie(selfie.cosmeticFilteringEngine); @@ -601,7 +719,7 @@ µBlock.destroySelfie = function() { vAPI.storage.remove('selfie'); this.asyncJobs.remove('toSelfie'); - //console.debug('storage.js > µBlock.destroySelfie()'); + //console.debug('µBlock.destroySelfie()'); }; /******************************************************************************/ @@ -626,6 +744,25 @@ /******************************************************************************/ +µBlock.assetUpdatedHandler = function(details) { + var path = details.path || ''; + if ( this.remoteBlacklists.hasOwnProperty(path) === false ) { + return; + } + var entry = this.remoteBlacklists[path]; + if ( entry.off ) { + return; + } + // Compile the list while we have the raw version in memory + //console.debug('µBlock.getCompiledFilterList/onRawListLoaded: compiling "%s"', path); + this.assets.put( + this.getCompiledFilterListPath(path), + this.compileFilters(details.content) + ); +}; + +/******************************************************************************/ + µBlock.updateCompleteHandler = function(details) { var µb = this; var updatedCount = details.updatedCount; @@ -659,3 +796,35 @@ onPSLReady(); } }; + +/******************************************************************************/ + +µBlock.assetCacheRemovedHandler = (function() { + var barrier = false; + + var handler = function(paths) { + if ( barrier ) { + return; + } + barrier = true; + var i = paths.length; + var path; + while ( i-- ) { + path = paths[i]; + if ( this.remoteBlacklists.hasOwnProperty(path) ) { + //console.debug('µBlock.assetCacheRemovedHandler: decompiling "%s"', path); + this.purgeCompiledFilterList(path); + continue; + } + if ( path === this.pslPath ) { + //console.debug('µBlock.assetCacheRemovedHandler: decompiling "%s"', path); + this.assets.purge('cache://compiled-publicsuffixlist'); + continue; + } + } + this.destroySelfie(); + barrier = false; + }; + + return handler; +})();