From e7d4aff2a2e16bce5b50e62eea84e8da79346def Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 5 Dec 2018 19:18:20 -0500 Subject: [PATCH] fix https://github.com/uBlockOrigin/uBlock-issues/issues/328 --- src/js/cachestorage.js | 102 ++++++++++++++++++++++++++++++++--------- src/js/lz4.js | 6 ++- src/js/start.js | 7 +++ src/js/storage.js | 31 +++++++------ 4 files changed, 110 insertions(+), 36 deletions(-) diff --git a/src/js/cachestorage.js b/src/js/cachestorage.js index 2af3f0743..d61e799a2 100644 --- a/src/js/cachestorage.js +++ b/src/js/cachestorage.js @@ -40,19 +40,28 @@ µBlock.cacheStorage = (function() { - // Firefox-specific: we use indexedDB because chrome.storage.local() has - // poor performance in Firefox. See: + const STORAGE_NAME = 'uBlock0CacheStorage'; + // https://bugzilla.mozilla.org/show_bug.cgi?id=1371255 - if ( vAPI.webextFlavor.soup.has('firefox') === false ) { + // Firefox-specific: we use indexedDB because chrome.storage.local() has + // poor performance in Firefox. + // https://github.com/uBlockOrigin/uBlock-issues/issues/328 + // Use IndexedDB for Chromium as well, to take advantage of LZ4 + // compression. + if ( + vAPI.webextFlavor.soup.has('firefox') === false && + vAPI.webextFlavor.soup.has('chromium') === false + ) { + // In case IndexedDB was used as cache storage, remove it. + indexedDB.deleteDatabase(STORAGE_NAME); return vAPI.cacheStorage; } - const STORAGE_NAME = 'uBlock0CacheStorage'; let db; let pendingInitialization; let dbByteLength; - let get = function get(input, callback) { + const get = function get(input, callback) { if ( typeof callback !== 'function' ) { return; } if ( input === null ) { return getAllFromDb(callback); @@ -69,23 +78,23 @@ return getFromDb(toRead, output, callback); }; - let set = function set(input, callback) { + const set = function set(input, callback) { putToDb(input, callback); }; - let remove = function remove(key, callback) { + const remove = function remove(key, callback) { deleteFromDb(key, callback); }; - let clear = function clear(callback) { + const clear = function clear(callback) { clearDb(callback); }; - let getBytesInUse = function getBytesInUse(keys, callback) { + const getBytesInUse = function getBytesInUse(keys, callback) { getDbSize(callback); }; - let api = { + const api = { get, set, remove, @@ -94,7 +103,7 @@ error: undefined }; - let genericErrorHandler = function(ev) { + const genericErrorHandler = function(ev) { let error = ev.target && ev.target.error; if ( error && error.name === 'QuotaExceededError' ) { api.error = error.name; @@ -102,10 +111,10 @@ console.error('[%s]', STORAGE_NAME, error && error.name); }; - function noopfn() { - } + const noopfn = function () { + }; - let getDb = function getDb() { + const getDb = function getDb() { if ( db instanceof IDBDatabase ) { return Promise.resolve(db); } @@ -165,7 +174,7 @@ return pendingInitialization; }; - let getFromDb = function(keys, keyvalStore, callback) { + const getFromDb = function(keys, keyvalStore, callback) { if ( typeof callback !== 'function' ) { return; } if ( keys.length === 0 ) { return callback(keyvalStore); } let promises = []; @@ -202,7 +211,7 @@ }); }; - let visitAllFromDb = function(visitFn) { + const visitAllFromDb = function(visitFn) { getDb().then(( ) => { if ( !db ) { return visitFn(); } let transaction = db.transaction(STORAGE_NAME); @@ -221,7 +230,7 @@ }); }; - let getAllFromDb = function(callback) { + const getAllFromDb = function(callback) { if ( typeof callback !== 'function' ) { return; } let promises = []; let keyvalStore = {}; @@ -245,7 +254,7 @@ }); }; - let getDbSize = function(callback) { + const getDbSize = function(callback) { if ( typeof callback !== 'function' ) { return; } if ( typeof dbByteLength === 'number' ) { return Promise.resolve().then(( ) => { @@ -280,7 +289,7 @@ // https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/transaction // https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/put - let putToDb = function(keyvalStore, callback) { + const putToDb = function(keyvalStore, callback) { if ( typeof callback !== 'function' ) { callback = noopfn; } @@ -326,7 +335,7 @@ }); }; - let deleteFromDb = function(input, callback) { + const deleteFromDb = function(input, callback) { if ( typeof callback !== 'function' ) { callback = noopfn; } @@ -356,7 +365,7 @@ }); }; - let clearDb = function(callback) { + const clearDb = function(callback) { if ( typeof callback !== 'function' ) { callback = noopfn; } @@ -383,6 +392,57 @@ // prime the db so that it's ready asap for next access. getDb(noopfn); + // https://github.com/uBlockOrigin/uBlock-issues/issues/328 + // Detect whether browser.storage.local was used as cache storage, + // and if so, move cache-related entries to the new storage. + { + const srcStorage = vAPI.cacheStorage; + const desStorage = api; + srcStorage.get( + [ 'assetCacheRegistry', 'assetSourceRegistry' ], + bin => { + if ( + bin instanceof Object === false || + bin.assetSourceRegistry instanceof Object === false + ) { + return; + } + desStorage.set(bin); + const toRemove = [ + 'assetCacheRegistry', + 'assetSourceRegistry', + 'resourcesSelfie', + 'selfie' + ]; + let toMigrate = 0; + const setEntry = function(assetKey, bin) { + if ( + bin instanceof Object && + bin[assetKey] !== undefined + ) { + desStorage.set(bin); + } + toMigrate -= 1; + if ( toMigrate === 0 ) { + srcStorage.remove(toRemove); + } + }; + for ( const key in bin.assetCacheRegistry ) { + if ( bin.assetCacheRegistry.hasOwnProperty(key) === false ) { + continue; + } + const assetKey = 'cache/' + key; + srcStorage.get(assetKey, setEntry.bind(null, assetKey)); + toMigrate += 1; + toRemove.push(assetKey); + } + if ( toMigrate === 0 ) { + srcStorage.remove(toRemove); + } + } + ); + } + return api; }()); diff --git a/src/js/lz4.js b/src/js/lz4.js index b81a45e12..1b73c6863 100644 --- a/src/js/lz4.js +++ b/src/js/lz4.js @@ -198,7 +198,11 @@ return { data: decodeValue(result.key, result.data) || result.data }; }); - } + }, + relinquish: function() { + ttlDelay = 1; + ttlManage(0); + }, }; /******************************************************************************/ diff --git a/src/js/start.js b/src/js/start.js index 28f6cf2e4..827c59b02 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -53,6 +53,13 @@ vAPI.app.onShutdown = function() { // - Schedule next update operation. var onAllReady = function() { + // Ensure that the resources allocated for decompression purpose (likely + // large buffers) are garbage-collectable immediately after launch. + // Otherwise I have observed that it may take quite a while before the + // garbage collection of these resources kicks in. Relinquishing as soon + // as possible ensure minimal memory usage baseline. + µb.lz4Codec.relinquish(); + µb.webRequest.start(); initializeTabs(); diff --git a/src/js/storage.js b/src/js/storage.js index 05dc0e729..4faca6795 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -608,15 +608,15 @@ if ( this.loadingFilterLists ) { return; } this.loadingFilterLists = true; - var µb = this, - filterlistsCount = 0, - loadedListKeys = []; + const µb = µBlock; + const loadedListKeys = []; + let filterlistsCount = 0; if ( typeof callback !== 'function' ) { callback = this.noopFunc; } - var onDone = function() { + const onDone = function() { µb.staticNetFilteringEngine.freeze(); µb.staticExtFilteringEngine.freeze(); µb.redirectEngine.freeze(); @@ -632,17 +632,19 @@ callback(); µb.selfieManager.destroy(); + µb.lz4Codec.relinquish(); + µb.loadingFilterLists = false; }; - var applyCompiledFilters = function(assetKey, compiled) { - var snfe = µb.staticNetFilteringEngine, - sxfe = µb.staticExtFilteringEngine, - acceptedCount = snfe.acceptedCount + sxfe.acceptedCount, + const applyCompiledFilters = function(assetKey, compiled) { + const snfe = µb.staticNetFilteringEngine; + const sxfe = µb.staticExtFilteringEngine; + let acceptedCount = snfe.acceptedCount + sxfe.acceptedCount, discardedCount = snfe.discardedCount + sxfe.discardedCount; µb.applyCompiledFilters(compiled, assetKey === µb.userFiltersPath); if ( µb.availableFilterLists.hasOwnProperty(assetKey) ) { - var entry = µb.availableFilterLists[assetKey]; + const entry = µb.availableFilterLists[assetKey]; entry.entryCount = snfe.acceptedCount + sxfe.acceptedCount - acceptedCount; entry.entryUsedCount = entry.entryCount - @@ -651,7 +653,7 @@ loadedListKeys.push(assetKey); }; - var onCompiledListLoaded = function(details) { + const onCompiledListLoaded = function(details) { applyCompiledFilters(details.assetKey, details.content); filterlistsCount -= 1; if ( filterlistsCount === 0 ) { @@ -659,7 +661,7 @@ } }; - var onFilterListsReady = function(lists) { + const onFilterListsReady = function(lists) { µb.availableFilterLists = lists; µb.redirectEngine.reset(); @@ -672,8 +674,8 @@ // 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 assetKey in lists ) { + const toLoad = []; + for ( const assetKey in lists ) { if ( lists.hasOwnProperty(assetKey) === false ) { continue; } if ( lists[assetKey].off ) { continue; } toLoad.push(assetKey); @@ -683,7 +685,7 @@ return onDone(); } - var i = toLoad.length; + let i = toLoad.length; while ( i-- ) { µb.getCompiledFilterList(toLoad[i], onCompiledListLoaded); } @@ -1021,6 +1023,7 @@ staticExtFilteringEngine: µb.staticExtFilteringEngine.toSelfie() }); µb.cacheStorage.set({ selfie: selfie }); + µb.lz4Codec.relinquish(); }; let load = function(callback) {