reorganize cache storage compression; workaround fix for #2812

This commit is contained in:
Raymond Hill 2018-08-11 10:39:43 -04:00
parent 8f1b4b52fd
commit 38aabc937a
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
13 changed files with 1002 additions and 313 deletions

View File

@ -58,7 +58,7 @@
}, },
"incognito": "split", "incognito": "split",
"manifest_version": 2, "manifest_version": 2,
"minimum_chrome_version": "45.0", "minimum_chrome_version": "47.0",
"name": "uBlock Origin", "name": "uBlock Origin",
"optional_permissions": [ "optional_permissions": [
"file:///*" "file:///*"

View File

@ -5,17 +5,19 @@
<title>uBlock Origin</title> <title>uBlock Origin</title>
</head> </head>
<body> <body>
<script src="lib/lz4/lz4-block-codec-any.js"></script>
<script src="lib/punycode.js"></script> <script src="lib/punycode.js"></script>
<script src="lib/publicsuffixlist.js"></script> <script src="lib/publicsuffixlist.js"></script>
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-background.js"></script> <script src="js/vapi-background.js"></script>
<script src="js/vapi-webrequest.js"></script><!-- Forks can pick the webext, chromium, or their own implementation --> <script src="js/vapi-webrequest.js"></script><!-- Forks can pick the webext, chromium, or their own implementation -->
<script src="js/vapi-cachestorage.js"></script><!-- Optional -->
<script src="js/background.js"></script> <script src="js/background.js"></script>
<script src="js/hntrie.js"></script> <script src="js/hntrie.js"></script>
<script src="js/utils.js"></script> <script src="js/utils.js"></script>
<script src="js/uritools.js"></script> <script src="js/uritools.js"></script>
<script src="js/lz4.js"></script>
<script src="js/cachestorage.js"></script>
<script src="js/assets.js"></script> <script src="js/assets.js"></script>
<script src="js/redirect-engine.js"></script> <script src="js/redirect-engine.js"></script>
<script src="js/dynamic-net-filtering.js"></script> <script src="js/dynamic-net-filtering.js"></script>

View File

@ -19,8 +19,6 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global WebAssembly */
'use strict'; 'use strict';
/******************************************************************************/ /******************************************************************************/
@ -304,7 +302,7 @@ var saveAssetSourceRegistry = (function() {
var timer; var timer;
var save = function() { var save = function() {
timer = undefined; timer = undefined;
vAPI.cacheStorage.set({ assetSourceRegistry: assetSourceRegistry }); µBlock.cacheStorage.set({ assetSourceRegistry: assetSourceRegistry });
}; };
return function(lazily) { return function(lazily) {
if ( timer !== undefined ) { if ( timer !== undefined ) {
@ -387,7 +385,7 @@ var getAssetSourceRegistry = function(callback) {
); );
}; };
vAPI.cacheStorage.get('assetSourceRegistry', function(bin) { µBlock.cacheStorage.get('assetSourceRegistry', function(bin) {
if ( !bin || !bin.assetSourceRegistry ) { if ( !bin || !bin.assetSourceRegistry ) {
createRegistry(); createRegistry();
return; return;
@ -411,247 +409,6 @@ api.unregisterAssetSource = function(assetKey) {
}); });
}; };
/*******************************************************************************
Experimental support for cache storage compression.
For background information on the topic, see:
https://github.com/uBlockOrigin/uBlock-issues/issues/141#issuecomment-407737186
**/
let lz4Codec = (function() {
let lz4wasmInstance;
let pendingInitialization;
let textEncoder, textDecoder;
let ttlCount = 0;
let ttlTimer;
const ttlDelay = 60 * 1000;
let init = function() {
if (
lz4wasmInstance === null ||
WebAssembly instanceof Object === false ||
typeof WebAssembly.instantiateStreaming !== 'function'
) {
lz4wasmInstance = null;
return Promise.resolve(null);
}
if ( lz4wasmInstance instanceof WebAssembly.Instance ) {
return Promise.resolve(lz4wasmInstance);
}
if ( pendingInitialization === undefined ) {
pendingInitialization = WebAssembly.instantiateStreaming(
fetch('lib/lz4-block-codec.wasm', { mode: 'same-origin' })
).then(result => {
pendingInitialization = undefined;
lz4wasmInstance = result && result.instance || null;
});
pendingInitialization.catch(( ) => {
lz4wasmInstance = null;
});
}
return pendingInitialization;
};
// We can't shrink memory usage of wasm instances, and in the current
// case memory usage can grow to a significant amount given that
// a single contiguous memory buffer is required to accommodate both
// input and output data. Thus a time-to-live implementation which
// will cause the wasm instance to be forgotten after enough time
// elapse without the instance being used.
let destroy = function() {
console.info(
'uBO: freeing lz4-block-codec.wasm instance (memory.buffer = %d kB)',
lz4wasmInstance.exports.memory.buffer.byteLength >>> 10
);
lz4wasmInstance = undefined;
textEncoder = textDecoder = undefined;
ttlCount = 0;
ttlTimer = undefined;
};
let ttlManage = function(count) {
if ( ttlTimer !== undefined ) {
clearTimeout(ttlTimer);
ttlTimer = undefined;
}
ttlCount += count;
if ( ttlCount > 0 ) { return; }
if ( lz4wasmInstance === null ) { return; }
ttlTimer = vAPI.setTimeout(destroy, ttlDelay);
};
let growMemoryTo = function(byteLength) {
let lz4api = lz4wasmInstance.exports;
let neededByteLength = lz4api.getLinearMemoryOffset() + byteLength;
let pageCountBefore = lz4api.memory.buffer.byteLength >>> 16;
let pageCountAfter = (neededByteLength + 65535) >>> 16;
if ( pageCountAfter > pageCountBefore ) {
lz4api.memory.grow(pageCountAfter - pageCountBefore);
}
return lz4api.memory;
};
let resolveEncodedValue = function(resolve, key, value) {
let t0 = window.performance.now();
let lz4api = lz4wasmInstance.exports;
let mem0 = lz4api.getLinearMemoryOffset();
let memory = growMemoryTo(mem0 + 65536 * 4);
let hashTable = new Int32Array(memory.buffer, mem0, 65536);
hashTable.fill(-65536, 0, 65536);
let hashTableSize = hashTable.byteLength;
if ( textEncoder === undefined ) {
textEncoder = new TextEncoder();
}
let inputArray = textEncoder.encode(value);
let inputSize = inputArray.byteLength;
let memSize =
hashTableSize +
inputSize +
8 + lz4api.lz4BlockEncodeBound(inputSize);
memory = growMemoryTo(memSize);
let inputMem = new Uint8Array(
memory.buffer,
mem0 + hashTableSize,
inputSize
);
inputMem.set(inputArray);
let outputSize = lz4api.lz4BlockEncode(
mem0 + hashTableSize,
inputSize,
mem0 + hashTableSize + inputSize + 8
);
if ( outputSize === 0 ) { resolve(value); }
let outputMem = new Uint8Array(
memory.buffer,
mem0 + hashTableSize + inputSize,
8 + outputSize
);
outputMem[0] = 0x18;
outputMem[1] = 0x4D;
outputMem[2] = 0x22;
outputMem[3] = 0x04;
outputMem[4] = (inputSize >>> 0) & 0xFF;
outputMem[5] = (inputSize >>> 8) & 0xFF;
outputMem[6] = (inputSize >>> 16) & 0xFF;
outputMem[7] = (inputSize >>> 24) & 0xFF;
console.info(
'uBO: [%s] compressed %d bytes into %d bytes in %s ms',
key,
inputSize,
outputSize,
(window.performance.now() - t0).toFixed(2)
);
resolve(new Blob([ outputMem ]));
};
let resolveDecodedValue = function(resolve, ev, key, value) {
let inputBuffer = ev.target.result;
if ( inputBuffer instanceof ArrayBuffer === false ) {
return resolve(value);
}
let t0 = window.performance.now();
let metadata = new Uint8Array(inputBuffer, 0, 8);
if (
metadata[0] !== 0x18 ||
metadata[1] !== 0x4D ||
metadata[2] !== 0x22 ||
metadata[3] !== 0x04
) {
return resolve(value);
}
let inputSize = inputBuffer.byteLength - 8;
let outputSize =
(metadata[4] << 0) |
(metadata[5] << 8) |
(metadata[6] << 16) |
(metadata[7] << 24);
let lz4api = lz4wasmInstance.exports;
let mem0 = lz4api.getLinearMemoryOffset();
let memSize = inputSize + outputSize;
let memory = growMemoryTo(memSize);
let inputArea = new Uint8Array(
memory.buffer,
mem0,
inputSize
);
inputArea.set(new Uint8Array(inputBuffer, 8, inputSize));
outputSize = lz4api.lz4BlockDecode(inputSize);
if ( outputSize === 0 ) {
return resolve(value);
}
let outputArea = new Uint8Array(
memory.buffer,
mem0 + inputSize,
outputSize
);
if ( textDecoder === undefined ) {
textDecoder = new TextDecoder();
}
value = textDecoder.decode(outputArea);
console.info(
'uBO: [%s] decompressed %d bytes into %d bytes in %s ms',
key,
inputSize,
outputSize,
(window.performance.now() - t0).toFixed(2)
);
resolve(value);
};
let encodeValue = function(key, value) {
if ( !lz4wasmInstance ) {
return Promise.resolve(value);
}
return new Promise(resolve => {
resolveEncodedValue(resolve, key, value);
});
};
let decodeValue = function(key, value) {
if ( !lz4wasmInstance ) {
return Promise.resolve(value);
}
return new Promise(resolve => {
let blobReader = new FileReader();
blobReader.onloadend = ev => {
resolveDecodedValue(resolve, ev, key, value);
};
blobReader.readAsArrayBuffer(value);
});
};
return {
encode: function(key, value) {
if ( typeof value !== 'string' || value.length < 4096 ) {
return Promise.resolve(value);
}
ttlManage(1);
return init().then(( ) => {
return encodeValue(key, value);
}).then(result => {
ttlManage(-1);
return result;
});
},
decode: function(key, value) {
if ( value instanceof Blob === false ) {
return Promise.resolve(value);
}
ttlManage(1);
return init().then(( ) => {
return decodeValue(key, value);
}).then(result => {
ttlManage(-1);
return result;
});
}
};
})();
/******************************************************************************* /*******************************************************************************
The purpose of the asset cache registry is to keep track of all assets The purpose of the asset cache registry is to keep track of all assets
@ -688,7 +445,7 @@ var getAssetCacheRegistry = function(callback) {
} }
}; };
vAPI.cacheStorage.get('assetCacheRegistry', function(bin) { µBlock.cacheStorage.get('assetCacheRegistry', function(bin) {
if ( bin && bin.assetCacheRegistry ) { if ( bin && bin.assetCacheRegistry ) {
assetCacheRegistry = bin.assetCacheRegistry; assetCacheRegistry = bin.assetCacheRegistry;
} }
@ -700,7 +457,7 @@ var saveAssetCacheRegistry = (function() {
var timer; var timer;
var save = function() { var save = function() {
timer = undefined; timer = undefined;
vAPI.cacheStorage.set({ assetCacheRegistry: assetCacheRegistry }); µBlock.cacheStorage.set({ assetCacheRegistry: assetCacheRegistry });
}; };
return function(lazily) { return function(lazily) {
if ( timer !== undefined ) { clearTimeout(timer); } if ( timer !== undefined ) { clearTimeout(timer); }
@ -735,16 +492,11 @@ var assetCacheRead = function(assetKey, callback) {
} }
entry.readTime = Date.now(); entry.readTime = Date.now();
saveAssetCacheRegistry(true); saveAssetCacheRegistry(true);
if ( µBlock.hiddenSettings.cacheStorageCompression !== true ) { reportBack(bin[internalKey]);
return reportBack(bin[internalKey]);
}
lz4Codec.decode(internalKey, bin[internalKey]).then(result => {
reportBack(result);
});
}; };
let onReady = function() { let onReady = function() {
vAPI.cacheStorage.get(internalKey, onAssetRead); µBlock.cacheStorage.get(internalKey, onAssetRead);
}; };
getAssetCacheRegistry(onReady); getAssetCacheRegistry(onReady);
@ -763,10 +515,7 @@ var assetCacheWrite = function(assetKey, details, callback) {
return assetCacheRemove(assetKey, callback); return assetCacheRemove(assetKey, callback);
} }
let reportBack = function(content) { let reportBack = function() {
let bin = { assetCacheRegistry: assetCacheRegistry };
bin[internalKey] = content;
vAPI.cacheStorage.set(bin);
let details = { assetKey: assetKey, content: content }; let details = { assetKey: assetKey, content: content };
if ( typeof callback === 'function' ) { if ( typeof callback === 'function' ) {
callback(details); callback(details);
@ -783,12 +532,9 @@ var assetCacheWrite = function(assetKey, details, callback) {
if ( details instanceof Object && typeof details.url === 'string' ) { if ( details instanceof Object && typeof details.url === 'string' ) {
entry.remoteURL = details.url; entry.remoteURL = details.url;
} }
if ( µBlock.hiddenSettings.cacheStorageCompression !== true ) { let bin = { assetCacheRegistry: assetCacheRegistry };
return reportBack(content); bin[internalKey] = content;
} µBlock.cacheStorage.set(bin, reportBack);
lz4Codec.encode(internalKey, content).then(result => {
reportBack(result);
});
}; };
getAssetCacheRegistry(onReady); getAssetCacheRegistry(onReady);
}; };
@ -810,9 +556,9 @@ var assetCacheRemove = function(pattern, callback) {
delete cacheDict[assetKey]; delete cacheDict[assetKey];
} }
if ( removedContent.length !== 0 ) { if ( removedContent.length !== 0 ) {
vAPI.cacheStorage.remove(removedContent); µBlock.cacheStorage.remove(removedContent);
var bin = { assetCacheRegistry: assetCacheRegistry }; var bin = { assetCacheRegistry: assetCacheRegistry };
vAPI.cacheStorage.set(bin); µBlock.cacheStorage.set(bin);
} }
if ( typeof callback === 'function' ) { if ( typeof callback === 'function' ) {
callback(); callback();
@ -852,7 +598,7 @@ var assetCacheMarkAsDirty = function(pattern, exclude, callback) {
} }
if ( mustSave ) { if ( mustSave ) {
var bin = { assetCacheRegistry: assetCacheRegistry }; var bin = { assetCacheRegistry: assetCacheRegistry };
vAPI.cacheStorage.set(bin); µBlock.cacheStorage.set(bin);
} }
if ( typeof callback === 'function' ) { if ( typeof callback === 'function' ) {
callback(); callback();
@ -892,7 +638,7 @@ var readUserAsset = function(assetKey, callback) {
var content = ''; var content = '';
if ( typeof bin['cached_asset_content://assets/user/filters.txt'] === 'string' ) { if ( typeof bin['cached_asset_content://assets/user/filters.txt'] === 'string' ) {
content = bin['cached_asset_content://assets/user/filters.txt']; content = bin['cached_asset_content://assets/user/filters.txt'];
vAPI.cacheStorage.remove('cached_asset_content://assets/user/filters.txt'); µBlock.cacheStorage.remove('cached_asset_content://assets/user/filters.txt');
} }
if ( typeof bin['assets/user/filters.txt'] === 'string' ) { if ( typeof bin['assets/user/filters.txt'] === 'string' ) {
content = bin['assets/user/filters.txt']; content = bin['assets/user/filters.txt'];

View File

@ -38,7 +38,7 @@
// has been added, for seamless migration of cache-related entries into // has been added, for seamless migration of cache-related entries into
// indexedDB. // indexedDB.
vAPI.cacheStorage = (function() { µBlock.cacheStorage = (function() {
// Firefox-specific: we use indexedDB because chrome.storage.local() has // Firefox-specific: we use indexedDB because chrome.storage.local() has
// poor performance in Firefox. See: // poor performance in Firefox. See:
@ -50,6 +50,7 @@ vAPI.cacheStorage = (function() {
const STORAGE_NAME = 'uBlock0CacheStorage'; const STORAGE_NAME = 'uBlock0CacheStorage';
let db; let db;
let pendingInitialization; let pendingInitialization;
let dbByteLength;
let get = function get(input, callback) { let get = function get(input, callback) {
if ( typeof callback !== 'function' ) { return; } if ( typeof callback !== 'function' ) { return; }
@ -81,11 +82,17 @@ vAPI.cacheStorage = (function() {
}; };
let getBytesInUse = function getBytesInUse(keys, callback) { let getBytesInUse = function getBytesInUse(keys, callback) {
// TODO: implement this getDbSize(callback);
callback(0);
}; };
let api = { get, set, remove, clear, getBytesInUse, error: undefined }; let api = {
get,
set,
remove,
clear,
getBytesInUse,
error: undefined
};
let genericErrorHandler = function(ev) { let genericErrorHandler = function(ev) {
let error = ev.target && ev.target.error; let error = ev.target && ev.target.error;
@ -158,20 +165,33 @@ vAPI.cacheStorage = (function() {
return pendingInitialization; return pendingInitialization;
}; };
let getFromDb = function(keys, keystore, callback) { let getFromDb = function(keys, keyvalStore, callback) {
if ( typeof callback !== 'function' ) { return; } if ( typeof callback !== 'function' ) { return; }
if ( keys.length === 0 ) { return callback(keystore); } if ( keys.length === 0 ) { return callback(keyvalStore); }
let promises = [];
let gotOne = function() { let gotOne = function() {
if ( typeof this.result === 'object' ) { if ( typeof this.result !== 'object' ) { return; }
keystore[this.result.key] = this.result.value; keyvalStore[this.result.key] = this.result.value;
} if ( this.result.value instanceof Blob === false ) { return; }
promises.push(
µBlock.lz4Codec.decode(
this.result.key,
this.result.value
).then(result => {
keyvalStore[result.key] = result.data;
})
);
}; };
getDb().then(( ) => { getDb().then(( ) => {
if ( !db ) { return callback(); } if ( !db ) { return callback(); }
let transaction = db.transaction(STORAGE_NAME); let transaction = db.transaction(STORAGE_NAME);
transaction.oncomplete = transaction.oncomplete =
transaction.onerror = transaction.onerror =
transaction.onabort = ( ) => callback(keystore); transaction.onabort = ( ) => {
Promise.all(promises).then(( ) => {
callback(keyvalStore);
});
};
let table = transaction.objectStore(STORAGE_NAME); let table = transaction.objectStore(STORAGE_NAME);
for ( let key of keys ) { for ( let key of keys ) {
let req = table.get(key); let req = table.get(key);
@ -182,43 +202,110 @@ vAPI.cacheStorage = (function() {
}); });
}; };
let getAllFromDb = function(callback) { let visitAllFromDb = function(visitFn) {
if ( typeof callback !== 'function' ) {
callback = noopfn;
}
getDb().then(( ) => { getDb().then(( ) => {
if ( !db ) { return callback(); } if ( !db ) { return visitFn(); }
let keystore = {};
let transaction = db.transaction(STORAGE_NAME); let transaction = db.transaction(STORAGE_NAME);
transaction.oncomplete = transaction.oncomplete =
transaction.onerror = transaction.onerror =
transaction.onabort = ( ) => callback(keystore); transaction.onabort = ( ) => visitFn();
let table = transaction.objectStore(STORAGE_NAME), let table = transaction.objectStore(STORAGE_NAME);
req = table.openCursor(); let req = table.openCursor();
req.onsuccess = function(ev) { req.onsuccess = function(ev) {
let cursor = ev.target.result; let cursor = ev.target && ev.target.result;
if ( !cursor ) { return; } if ( !cursor ) { return; }
keystore[cursor.key] = cursor.value; let entry = cursor.value;
visitFn(entry);
cursor.continue(); cursor.continue();
}; };
}); });
}; };
let getAllFromDb = function(callback) {
if ( typeof callback !== 'function' ) { return; }
let promises = [];
let keyvalStore = {};
visitAllFromDb(entry => {
if ( entry === undefined ) {
Promise.all(promises).then(( ) => {
callback(keyvalStore);
});
return;
}
keyvalStore[entry.key] = entry.value;
if ( entry.value instanceof Blob === false ) { return; }
promises.push(
µBlock.lz4Codec.decode(
entry.key,
entry.value
).then(result => {
keyvalStore[result.key] = result.value;
})
);
});
};
let getDbSize = function(callback) {
if ( typeof callback !== 'function' ) { return; }
if ( typeof dbByteLength === 'number' ) {
return Promise.resolve().then(( ) => {
callback(dbByteLength);
});
}
let textEncoder = new TextEncoder();
let totalByteLength = 0;
visitAllFromDb(entry => {
if ( entry === undefined ) {
dbByteLength = totalByteLength;
return callback(totalByteLength);
}
let value = entry.value;
if ( typeof value === 'string' ) {
totalByteLength += textEncoder.encode(value).byteLength;
} else if ( value instanceof Blob ) {
totalByteLength += value.size;
} else {
totalByteLength += textEncoder.encode(JSON.stringify(value)).byteLength;
}
if ( typeof entry.key === 'string' ) {
totalByteLength += textEncoder.encode(entry.key).byteLength;
}
});
};
// https://github.com/uBlockOrigin/uBlock-issues/issues/141 // https://github.com/uBlockOrigin/uBlock-issues/issues/141
// Mind that IDBDatabase.transaction() and IDBObjectStore.put() // Mind that IDBDatabase.transaction() and IDBObjectStore.put()
// can throw: // can throw:
// https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/transaction // https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/transaction
// https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/put // https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/put
let putToDb = function(keystore, callback) { let putToDb = function(keyvalStore, callback) {
if ( typeof callback !== 'function' ) { if ( typeof callback !== 'function' ) {
callback = noopfn; callback = noopfn;
} }
let keys = Object.keys(keystore); let keys = Object.keys(keyvalStore);
if ( keys.length === 0 ) { return callback(); } if ( keys.length === 0 ) { return callback(); }
getDb().then(( ) => { let promises = [ getDb() ];
let entries = [];
let dontCompress = µBlock.hiddenSettings.cacheStorageCompression !== true;
let handleEncodingResult = result => {
entries.push({ key: result.key, value: result.data });
};
for ( let key of keys ) {
let data = keyvalStore[key];
if ( typeof data !== 'string' || dontCompress ) {
entries.push({ key, value: data });
continue;
}
promises.push(
µBlock.lz4Codec.encode(key, data).then(handleEncodingResult)
);
}
Promise.all(promises).then(( ) => {
if ( !db ) { return callback(); } if ( !db ) { return callback(); }
let finish = ( ) => { let finish = ( ) => {
dbByteLength = undefined;
if ( callback === undefined ) { return; } if ( callback === undefined ) { return; }
let cb = callback; let cb = callback;
callback = undefined; callback = undefined;
@ -230,12 +317,8 @@ vAPI.cacheStorage = (function() {
transaction.onerror = transaction.onerror =
transaction.onabort = finish; transaction.onabort = finish;
let table = transaction.objectStore(STORAGE_NAME); let table = transaction.objectStore(STORAGE_NAME);
for ( let key of keys ) { for ( let entry of entries ) {
let entry = {};
entry.key = key;
entry.value = keystore[key];
table.put(entry); table.put(entry);
entry = undefined;
} }
} catch (ex) { } catch (ex) {
finish(); finish();
@ -252,6 +335,7 @@ vAPI.cacheStorage = (function() {
getDb().then(db => { getDb().then(db => {
if ( !db ) { return callback(); } if ( !db ) { return callback(); }
let finish = ( ) => { let finish = ( ) => {
dbByteLength = undefined;
if ( callback === undefined ) { return; } if ( callback === undefined ) { return; }
let cb = callback; let cb = callback;
callback = undefined; callback = undefined;
@ -279,6 +363,7 @@ vAPI.cacheStorage = (function() {
getDb().then(db => { getDb().then(db => {
if ( !db ) { return callback(); } if ( !db ) { return callback(); }
let finish = ( ) => { let finish = ( ) => {
dbByteLength = undefined;
if ( callback === undefined ) { return; } if ( callback === undefined ) { return; }
let cb = callback; let cb = callback;
callback = undefined; callback = undefined;

193
src/js/lz4.js Normal file
View File

@ -0,0 +1,193 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2018-present Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
/* global lz4BlockCodec */
'use strict';
/*******************************************************************************
Experimental support for storage compression.
For background information on the topic, see:
https://github.com/uBlockOrigin/uBlock-issues/issues/141#issuecomment-407737186
**/
µBlock.lz4Codec = (function() { // >>>> Start of private namespace
/******************************************************************************/
let lz4CodecInstance;
let pendingInitialization;
let textEncoder, textDecoder;
let ttlCount = 0;
let ttlTimer;
const ttlDelay = 60 * 1000;
let init = function() {
if ( lz4CodecInstance === null ) {
return Promise.resolve(null);
}
if ( lz4CodecInstance !== undefined ) {
return Promise.resolve(lz4CodecInstance);
}
if ( pendingInitialization === undefined ) {
pendingInitialization = lz4BlockCodec.createInstance()
.then(instance => {
lz4CodecInstance = instance;
pendingInitialization = undefined;
});
}
return pendingInitialization;
};
// We can't shrink memory usage of lz4 codec instances, and in the
// current case memory usage can grow to a significant amount given
// that a single contiguous memory buffer is required to accommodate
// both input and output data. Thus a time-to-live implementation
// which will cause the wasm instance to be forgotten after enough
// time elapse without the instance being used.
let destroy = function() {
console.info('uBO: freeing lz4-block-codec instance');
lz4CodecInstance = undefined;
textEncoder = textDecoder = undefined;
ttlCount = 0;
ttlTimer = undefined;
};
let ttlManage = function(count) {
if ( ttlTimer !== undefined ) {
clearTimeout(ttlTimer);
ttlTimer = undefined;
}
ttlCount += count;
if ( ttlCount > 0 ) { return; }
if ( lz4CodecInstance === null ) { return; }
ttlTimer = vAPI.setTimeout(destroy, ttlDelay);
};
let uint8ArrayFromBlob = function(key, data) {
if ( data instanceof Blob === false ) {
return Promise.resolve({ key, data });
}
return new Promise(resolve => {
let blobReader = new FileReader();
blobReader.onloadend = ev => {
resolve({ key, data: new Uint8Array(ev.target.result) });
};
blobReader.readAsArrayBuffer(data);
});
};
let encodeValue = function(key, value) {
if ( !lz4CodecInstance ) { return; }
let t0 = window.performance.now();
if ( textEncoder === undefined ) {
textEncoder = new TextEncoder();
}
let inputArray = textEncoder.encode(value);
let inputSize = inputArray.byteLength;
let outputArray = lz4CodecInstance.encodeBlock(inputArray, 8);
outputArray[0] = 0x18;
outputArray[1] = 0x4D;
outputArray[2] = 0x22;
outputArray[3] = 0x04;
outputArray[4] = (inputSize >>> 0) & 0xFF;
outputArray[5] = (inputSize >>> 8) & 0xFF;
outputArray[6] = (inputSize >>> 16) & 0xFF;
outputArray[7] = (inputSize >>> 24) & 0xFF;
console.info(
'uBO: [%s] compressed %d bytes into %d bytes in %s ms',
key,
inputArray.byteLength,
outputArray.byteLength,
(window.performance.now() - t0).toFixed(2)
);
return outputArray;
};
let decodeValue = function(key, inputArray) {
if ( !lz4CodecInstance ) { return; }
let t0 = window.performance.now();
if (
inputArray[0] !== 0x18 || inputArray[1] !== 0x4D ||
inputArray[2] !== 0x22 || inputArray[3] !== 0x04
) {
return;
}
let outputSize =
(inputArray[4] << 0) | (inputArray[5] << 8) |
(inputArray[6] << 16) | (inputArray[7] << 24);
let outputArray = lz4CodecInstance.decodeBlock(inputArray, 8, outputSize);
if ( textDecoder === undefined ) {
textDecoder = new TextDecoder();
}
let value = textDecoder.decode(outputArray);
console.info(
'uBO: [%s] decompressed %d bytes into %d bytes in %s ms',
key,
inputArray.byteLength,
outputSize,
(window.performance.now() - t0).toFixed(2)
);
return value;
};
return {
encode: function(key, data) {
if ( typeof data !== 'string' || data.length < 4096 ) {
return Promise.resolve({ key, data });
}
ttlManage(1);
return init().then(( ) => {
ttlManage(-1);
let encoded = encodeValue(key, data) || data;
if ( encoded instanceof Uint8Array ) {
encoded = new Blob([ encoded ]);
}
return { key, data: encoded };
});
},
decode: function(key, data) {
if ( data instanceof Blob === false ) {
return Promise.resolve({ key, data });
}
ttlManage(1);
return Promise.all([
init(),
uint8ArrayFromBlob(key, data)
]).then(results => {
ttlManage(-1);
let result = results[1];
return {
key: result.key,
data: decodeValue(result.key, result.data) || result.data
};
});
}
};
/******************************************************************************/
})(); // <<<< End of private namespace

View File

@ -787,7 +787,7 @@ var restoreUserData = function(request) {
// If we are going to restore all, might as well wipe out clean local // If we are going to restore all, might as well wipe out clean local
// storage // storage
vAPI.cacheStorage.clear(); µb.cacheStorage.clear();
vAPI.storage.clear(onAllRemoved); vAPI.storage.clear(onAllRemoved);
vAPI.localStorage.removeItem('immediateHiddenSettings'); vAPI.localStorage.removeItem('immediateHiddenSettings');
}; };
@ -803,9 +803,9 @@ var resetUserData = function() {
vAPI.app.restart(); vAPI.app.restart();
} }
}; };
vAPI.cacheStorage.clear(countdown); // 1 µb.cacheStorage.clear(countdown); // 1
vAPI.storage.clear(countdown); // 2 vAPI.storage.clear(countdown); // 2
µb.saveLocalSettings(countdown); // 3 µb.saveLocalSettings(countdown); // 3
vAPI.localStorage.removeItem('immediateHiddenSettings'); vAPI.localStorage.removeItem('immediateHiddenSettings');
}; };

View File

@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
uBlock Origin - a browser extension to block requests. uBlock Origin - a browser extension to block requests.
Copyright (C) 2015-2018 Raymond Hill Copyright (C) 2015-present Raymond Hill
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -504,7 +504,7 @@ RedirectEngine.prototype.resourcesFromString = function(text) {
var resourcesSelfieVersion = 3; var resourcesSelfieVersion = 3;
RedirectEngine.prototype.selfieFromResources = function() { RedirectEngine.prototype.selfieFromResources = function() {
vAPI.cacheStorage.set({ µBlock.cacheStorage.set({
resourcesSelfie: { resourcesSelfie: {
version: resourcesSelfieVersion, version: resourcesSelfieVersion,
resources: Array.from(this.resources) resources: Array.from(this.resources)
@ -534,11 +534,11 @@ RedirectEngine.prototype.resourcesFromSelfie = function(callback) {
callback(true); callback(true);
}; };
vAPI.cacheStorage.get('resourcesSelfie', onSelfieReady); µBlock.cacheStorage.get('resourcesSelfie', onSelfieReady);
}; };
RedirectEngine.prototype.invalidateResourcesSelfie = function() { RedirectEngine.prototype.invalidateResourcesSelfie = function() {
vAPI.cacheStorage.remove('resourcesSelfie'); µBlock.cacheStorage.remove('resourcesSelfie');
}; };
/******************************************************************************/ /******************************************************************************/

View File

@ -29,14 +29,35 @@
if ( typeof callback !== 'function' ) { if ( typeof callback !== 'function' ) {
callback = this.noopFunc; callback = this.noopFunc;
} }
var getBytesInUseHandler = function(bytesInUse) { let bytesInUse;
let countdown = 0;
let process = count => {
if ( typeof count === 'number' ) {
if ( bytesInUse === undefined ) {
bytesInUse = 0;
}
bytesInUse += count;
}
countdown -= 1;
if ( countdown > 0 ) { return; }
µBlock.storageUsed = bytesInUse; µBlock.storageUsed = bytesInUse;
callback(bytesInUse); callback(bytesInUse);
}; };
// Not all platforms implement this method. // Not all platforms implement this method.
if ( vAPI.storage.getBytesInUse instanceof Function ) { if ( vAPI.storage.getBytesInUse instanceof Function ) {
vAPI.storage.getBytesInUse(null, getBytesInUseHandler); countdown += 1;
} else { vAPI.storage.getBytesInUse(null, process);
}
if (
this.cacheStorage !== vAPI.storage &&
this.cacheStorage.getBytesInUse instanceof Function
) {
countdown += 1;
this.cacheStorage.getBytesInUse(null, process);
}
if ( countdown === 0 ) {
callback(); callback();
} }
}; };
@ -1032,11 +1053,11 @@
redirectEngine: µb.redirectEngine.toSelfie(), redirectEngine: µb.redirectEngine.toSelfie(),
staticExtFilteringEngine: µb.staticExtFilteringEngine.toSelfie() staticExtFilteringEngine: µb.staticExtFilteringEngine.toSelfie()
}); });
vAPI.cacheStorage.set({ selfie: selfie }); µb.cacheStorage.set({ selfie: selfie });
}; };
let load = function(callback) { let load = function(callback) {
vAPI.cacheStorage.get('selfie', function(bin) { µb.cacheStorage.get('selfie', function(bin) {
if ( if (
bin instanceof Object === false || bin instanceof Object === false ||
typeof bin.selfie !== 'string' typeof bin.selfie !== 'string'
@ -1067,7 +1088,7 @@
clearTimeout(timer); clearTimeout(timer);
timer = null; timer = null;
} }
vAPI.cacheStorage.remove('selfie'); µb.cacheStorage.remove('selfie');
timer = vAPI.setTimeout(create, µb.selfieAfter); timer = vAPI.setTimeout(create, µb.selfieAfter);
}; };

Binary file not shown.

View File

@ -0,0 +1,171 @@
/*******************************************************************************
lz4-block-codec-any.js
A wrapper to instanciate a wasm- and/or js-based LZ4 block
encoder/decoder.
Copyright (C) 2018 Raymond Hill
BSD-2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Home: https://github.com/gorhill/lz4-wasm
I used the same license as the one picked by creator of LZ4 out of respect
for his creation, see https://lz4.github.io/lz4/
*/
'use strict';
/******************************************************************************/
(function(context) { // >>>> Start of private namespace
/******************************************************************************/
let wd = (function() {
let url = document.currentScript.src;
let match = /[^\/]+$/.exec(url);
return match !== null ?
url.slice(0, match.index) :
'';
})();
let removeScript = function(script) {
if ( !script ) { return; }
if ( script.parentNode === null ) { return; }
script.parentNode.removeChild(script);
};
let createInstanceWASM = function() {
if ( context.LZ4BlockWASM instanceof Function ) {
let instance = new context.LZ4BlockWASM();
return instance.init().then(( ) => { return instance; });
}
if ( context.LZ4BlockWASM === null ) {
return Promise.resolve(null);
}
return new Promise((resolve, reject) => {
let script = document.createElement('script');
script.src = wd + 'lz4-block-codec-wasm.js';
script.addEventListener('load', ( ) => {
if ( context.LZ4BlockWASM instanceof Function === false ) {
context.LZ4BlockWASM = null;
context.LZ4BlockWASM = undefined;
resolve(null);
} else {
let instance = new context.LZ4BlockWASM();
instance.init()
.then(( ) => {
resolve(instance);
})
.catch(error => {
reject(error);
});
}
});
script.addEventListener('error', ( ) => {
context.LZ4BlockWASM = null;
resolve(null);
});
document.head.appendChild(script);
removeScript(script);
});
};
let createInstanceJS = function() {
if ( context.LZ4BlockJS instanceof Function ) {
let instance = new context.LZ4BlockJS();
return instance.init().then(( ) => { return instance; });
}
if ( context.LZ4BlockJS === null ) {
return Promise.resolve(null);
}
return new Promise((resolve, reject) => {
let script = document.createElement('script');
script.src = wd + 'lz4-block-codec-js.js';
script.addEventListener('load', ( ) => {
if ( context.LZ4BlockJS instanceof Function === false ) {
context.LZ4BlockJS = null;
resolve(null);
} else {
let instance = new context.LZ4BlockJS();
instance.init()
.then(( ) => {
resolve(instance);
})
.catch(error => {
reject(error);
});
}
});
script.addEventListener('error', ( ) => {
context.LZ4BlockJS = null;
resolve(null);
});
document.head.appendChild(script);
removeScript(script);
});
};
/******************************************************************************/
context.lz4BlockCodec = {
createInstance: function(flavor) {
let instantiator;
if ( flavor === 'wasm' ) {
instantiator = createInstanceWASM;
} else if ( flavor === 'js' ) {
instantiator = createInstanceJS;
} else {
instantiator = createInstanceWASM || createInstanceJS;
}
return (instantiator)()
.then(instance => {
if ( instance ) { return instance; }
if ( flavor === undefined ) {
return createInstanceJS();
}
return null;
})
.catch(( ) => {
if ( flavor === undefined ) {
return createInstanceJS();
}
return null;
});
},
reset: function() {
context.LZ4BlockWASM = undefined;
context.LZ4BlockJS = undefined;
}
};
/******************************************************************************/
})(this || self); // <<<< End of private namespace
/******************************************************************************/

View File

@ -0,0 +1,281 @@
/*******************************************************************************
lz4-block-codec-js.js
A javascript wrapper around a pure javascript implementation of
LZ4 block format codec.
Copyright (C) 2018 Raymond Hill
BSD-2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Home: https://github.com/gorhill/lz4-wasm
I used the same license as the one picked by creator of LZ4 out of respect
for his creation, see https://lz4.github.io/lz4/
*/
'use strict';
/******************************************************************************/
(function(context) { // >>>> Start of private namespace
/******************************************************************************/
let encodeBound = function(size) {
return size > 0x7E000000 ?
0 :
size + size / 255 + 16;
};
let encodeBlock = function(instance, iBuf, outOffset) {
let iLen = iBuf.byteLength;
if ( iLen >= 0x7E000000 ) { throw new TypeError(); }
// "The last match must start at least 12 bytes before end of block"
let lastMatchPos = iLen - 12;
// "The last 5 bytes are always literals"
let lastLiteralPos = iLen - 5;
if ( instance.hashTable === undefined ) {
instance.hashTable = new Int32Array(65536);
}
instance.hashTable.fill(-65536);
if ( iBuf instanceof ArrayBuffer ) {
iBuf = new Uint8Array(iBuf);
}
let oBuf = new Uint8Array(outOffset + encodeBound(iLen));
let iPos = 0;
let oPos = outOffset;
let anchorPos = 0;
// sequence-finding loop
for (;;) {
let refPos;
let mOffset;
let sequence = iBuf[iPos] << 8 | iBuf[iPos+1] << 16 | iBuf[iPos+2] << 24;
// match-finding loop
while ( iPos <= lastMatchPos ) {
sequence = sequence >>> 8 | iBuf[iPos+3] << 24;
let hash = (sequence * 0x9E37 & 0xFFFF) + (sequence * 0x79B1 >>> 16) & 0xFFFF;
refPos = instance.hashTable[hash];
instance.hashTable[hash] = iPos;
mOffset = iPos - refPos;
if (
mOffset < 65536 &&
iBuf[refPos+0] === ((sequence ) & 0xFF) &&
iBuf[refPos+1] === ((sequence >>> 8) & 0xFF) &&
iBuf[refPos+2] === ((sequence >>> 16) & 0xFF) &&
iBuf[refPos+3] === ((sequence >>> 24) & 0xFF)
) {
break;
}
iPos += 1;
}
// no match found
if ( iPos > lastMatchPos ) { break; }
// match found
let lLen = iPos - anchorPos;
let mLen = iPos;
iPos += 4; refPos += 4;
while ( iPos < lastLiteralPos && iBuf[iPos] === iBuf[refPos] ) {
iPos += 1; refPos += 1;
}
mLen = iPos - mLen;
let token = mLen < 19 ? mLen - 4 : 15;
// write token, length of literals if needed
if ( lLen >= 15 ) {
oBuf[oPos++] = 0xF0 | token;
let l = lLen - 15;
while ( l >= 255 ) {
oBuf[oPos++] = 255;
l -= 255;
}
oBuf[oPos++] = l;
} else {
oBuf[oPos++] = (lLen << 4) | token;
}
// write literals
while ( lLen-- ) {
oBuf[oPos++] = iBuf[anchorPos++];
}
if ( mLen === 0 ) { break; }
// write offset of match
oBuf[oPos+0] = mOffset;
oBuf[oPos+1] = mOffset >>> 8;
oPos += 2;
// write length of match if needed
if ( mLen >= 19 ) {
let l = mLen - 19;
while ( l >= 255 ) {
oBuf[oPos++] = 255;
l -= 255;
}
oBuf[oPos++] = l;
}
anchorPos = iPos;
}
// last sequence is literals only
let lLen = iLen - anchorPos;
if ( lLen >= 15 ) {
oBuf[oPos++] = 0xF0;
let l = lLen - 15;
while ( l >= 255 ) {
oBuf[oPos++] = 255;
l -= 255;
}
oBuf[oPos++] = l;
} else {
oBuf[oPos++] = lLen << 4;
}
while ( lLen-- ) {
oBuf[oPos++] = iBuf[anchorPos++];
}
return new Uint8Array(oBuf.buffer, 0, oPos);
};
let decodeBlock = function(instance, iBuf, iOffset, oLen) {
let iLen = iBuf.byteLength;
if (
instance.outputBuffer === undefined ||
instance.outputBuffer.byteLength < oLen
) {
instance.outputBuffer = new ArrayBuffer(oLen + 0xFFFF & 0x7FFF0000);
}
let oBuf = new Uint8Array(instance.outputBuffer, 0, oLen);
let iPos = iOffset, oPos = 0;
while ( iPos < iLen ) {
let token = iBuf[iPos++];
// literals
let clen = token >>> 4;
// length of literals
if ( clen !== 0 ) {
if ( clen === 15 ) {
let l;
for (;;) {
l = iBuf[iPos++];
if ( l !== 255 ) { break; }
clen += 255;
}
clen += l;
}
// copy literals
let end = iPos + clen;
while ( iPos < end ) {
oBuf[oPos++] = iBuf[iPos++];
}
if ( iPos === iLen ) { break; }
}
// match
let mOffset = iBuf[iPos+0] | (iBuf[iPos+1] << 8);
if ( mOffset === 0 || mOffset > oPos ) { return 0; }
iPos += 2;
// length of match
clen = (token & 0x0F) + 4;
if ( clen === 19 ) {
let l;
for (;;) {
l = iBuf[iPos++];
if ( l !== 255 ) { break; }
clen += 255;
}
clen += l;
}
// copy match
let mPos = oPos - mOffset;
let end = oPos + clen;
while ( oPos < end ) {
oBuf[oPos++] = oBuf[mPos++];
}
}
return oBuf;
};
/******************************************************************************/
context.LZ4BlockJS = function() {
this.hashTable = undefined;
this.outputBuffer = undefined;
};
context.LZ4BlockJS.prototype = {
flavor: 'js',
init: function() {
return Promise.resolve();
},
reset: function() {
this.hashTable = undefined;
this.outputBuffer = undefined;
},
encodeBlock: function(input, outputOffset) {
if ( input instanceof ArrayBuffer ) {
input = new Uint8Array(input);
} else if ( input instanceof Uint8Array === false ) {
throw new TypeError();
}
return encodeBlock(this, input, outputOffset);
},
decodeBlock: function(input, inputOffset, outputSize) {
if ( input instanceof ArrayBuffer ) {
input = new Uint8Array(input);
} else if ( input instanceof Uint8Array === false ) {
throw new TypeError();
}
return decodeBlock(this, input, inputOffset, outputSize);
}
};
/******************************************************************************/
})(this || self); // <<<< End of private namespace
/******************************************************************************/

View File

@ -0,0 +1,190 @@
/*******************************************************************************
lz4-block-codec-wasm.js
A javascript wrapper around a WebAssembly implementation of
LZ4 block format codec.
Copyright (C) 2018 Raymond Hill
BSD-2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Home: https://github.com/gorhill/lz4-wasm
I used the same license as the one picked by creator of LZ4 out of respect
for his creation, see https://lz4.github.io/lz4/
*/
/* global WebAssembly */
'use strict';
/******************************************************************************/
(function(context) { // >>>> Start of private namespace
/******************************************************************************/
let wd = (function() {
let url = document.currentScript.src;
let match = /[^\/]+$/.exec(url);
return match !== null ?
url.slice(0, match.index) :
'';
})();
let growMemoryTo = function(wasmInstance, byteLength) {
let lz4api = wasmInstance.exports;
let neededByteLength = lz4api.getLinearMemoryOffset() + byteLength;
let pageCountBefore = lz4api.memory.buffer.byteLength >>> 16;
let pageCountAfter = (neededByteLength + 65535) >>> 16;
if ( pageCountAfter > pageCountBefore ) {
lz4api.memory.grow(pageCountAfter - pageCountBefore);
}
return lz4api.memory.buffer;
};
let encodeBlock = function(wasmInstance, inputArray, outputOffset) {
let lz4api = wasmInstance.exports;
let mem0 = lz4api.getLinearMemoryOffset();
let hashTableSize = 65536 * 4;
let inputSize = inputArray.byteLength;
if ( inputSize >= 0x7E000000 ) { throw new TypeError(); }
let memSize =
hashTableSize +
inputSize +
outputOffset + lz4api.lz4BlockEncodeBound(inputSize);
let memBuffer = growMemoryTo(wasmInstance, memSize);
let hashTable = new Int32Array(memBuffer, mem0, 65536);
hashTable.fill(-65536, 0, 65536);
let inputMem = new Uint8Array(memBuffer, mem0 + hashTableSize, inputSize);
inputMem.set(inputArray);
let outputSize = lz4api.lz4BlockEncode(
mem0 + hashTableSize,
inputSize,
mem0 + hashTableSize + inputSize + outputOffset
);
if ( outputSize === 0 ) {
inputSize = 0 - inputSize;
}
let outputArray = new Uint8Array(
memBuffer,
mem0 + hashTableSize + inputSize,
outputOffset + outputSize
);
return outputArray;
};
let decodeBlock = function(wasmInstance, inputArray, inputOffset, outputSize) {
let inputSize = inputArray.byteLength;
let lz4api = wasmInstance.exports;
let mem0 = lz4api.getLinearMemoryOffset();
let memSize = inputSize + outputSize;
let memBuffer = growMemoryTo(wasmInstance, memSize);
let inputArea = new Uint8Array(memBuffer, mem0, inputSize);
inputArea.set(inputArray);
outputSize = lz4api.lz4BlockDecode(
mem0 + inputOffset,
inputSize - inputOffset,
mem0 + inputSize
);
if ( outputSize === 0 ) {
throw new Error('LZ4BlockWASM: block-level compression failed');
}
return new Uint8Array(memBuffer, mem0 + inputSize, outputSize);
};
/******************************************************************************/
context.LZ4BlockWASM = function() {
this.lz4wasmInstance = undefined;
};
context.LZ4BlockWASM.prototype = {
flavor: 'wasm',
init: function() {
if ( this.lz4wasmInstance instanceof WebAssembly.Instance ) {
return Promise.resolve(this.lz4wasmInstance);
}
if (
this.lz4wasmInstance === null ||
WebAssembly instanceof Object === false ||
typeof WebAssembly.instantiateStreaming !== 'function'
) {
this.lz4wasmInstance = null;
return Promise.resolve(null);
}
if ( this.lz4wasmInstance === undefined ) {
this.lz4wasmInstance = WebAssembly.instantiateStreaming(
fetch(wd + 'lz4-block-codec.wasm', { mode: 'same-origin' })
).then(result => {
this.lz4wasmInstance = undefined;
this.lz4wasmInstance = result && result.instance || null;
if ( this.lz4wasmInstance !== null ) { return this; }
return null;
});
this.lz4wasmInstance.catch(( ) => {
this.lz4wasmInstance = null;
return null;
});
}
return this.lz4wasmInstance;
},
reset: function() {
this.lz4wasmInstance = undefined;
},
encodeBlock: function(input, outputOffset) {
if ( this.lz4wasmInstance instanceof WebAssembly.Instance === false ) {
throw new Error('LZ4BlockWASM: not initialized');
}
if ( input instanceof ArrayBuffer ) {
input = new Uint8Array(input);
} else if ( input instanceof Uint8Array === false ) {
throw new TypeError();
}
return encodeBlock(this.lz4wasmInstance, input, outputOffset);
},
decodeBlock: function(input, inputOffset, outputSize) {
if ( this.lz4wasmInstance instanceof WebAssembly.Instance === false ) {
throw new Error('LZ4BlockWASM: not initialized');
}
if ( input instanceof ArrayBuffer ) {
input = new Uint8Array(input);
} else if ( input instanceof Uint8Array === false ) {
throw new TypeError();
}
return decodeBlock(this.lz4wasmInstance, input, inputOffset, outputSize);
}
};
/******************************************************************************/
})(this || self); // <<<< End of private namespace
/******************************************************************************/

Binary file not shown.