mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-05 18:32:30 +01:00
69c87c5117
The Promise chain was not properly designed for WASM module loading. This became apparent when removing WASM modules from Opera build[1]. The problem was that errors thrown by fetch() -- used to load WASM modules -- were not properly handled. [1] Opera refuses updating uBO if there are unrecognized file types in the package, and `.wasm`/`.wat` files are not recognized by Opera uploader.
211 lines
6.9 KiB
JavaScript
211 lines
6.9 KiB
JavaScript
/*******************************************************************************
|
|
|
|
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;
|
|
let ttlDelay = 60000;
|
|
|
|
const init = function() {
|
|
ttlDelay = µBlock.hiddenSettings.autoUpdateAssetFetchPeriod * 1000 + 15000;
|
|
if ( lz4CodecInstance === null ) {
|
|
return Promise.resolve(null);
|
|
}
|
|
if ( lz4CodecInstance !== undefined ) {
|
|
return Promise.resolve(lz4CodecInstance);
|
|
}
|
|
if ( pendingInitialization === undefined ) {
|
|
let flavor;
|
|
if ( µBlock.hiddenSettings.disableWebAssembly === true ) {
|
|
flavor = 'js';
|
|
}
|
|
pendingInitialization = lz4BlockCodec.createInstance(flavor)
|
|
.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.
|
|
|
|
const destroy = function() {
|
|
//if ( lz4CodecInstance !== undefined ) {
|
|
// console.info(
|
|
// 'uBO: freeing lz4-block-codec instance (%s KB)',
|
|
// lz4CodecInstance.bytesInUse() >>> 10
|
|
// );
|
|
//}
|
|
lz4CodecInstance = undefined;
|
|
textEncoder = textDecoder = undefined;
|
|
ttlCount = 0;
|
|
ttlTimer = undefined;
|
|
};
|
|
|
|
const 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);
|
|
};
|
|
|
|
const 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);
|
|
});
|
|
};
|
|
|
|
const 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);
|
|
if ( outputArray instanceof Uint8Array === false ) { return; }
|
|
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 KB => %d KB (%s%%) in %s ms',
|
|
// key,
|
|
// inputArray.byteLength >> 10,
|
|
// outputArray.byteLength >> 10,
|
|
// (outputArray.byteLength / inputArray.byteLength * 100).toFixed(0),
|
|
// (window.performance.now() - t0).toFixed(1)
|
|
//);
|
|
return outputArray;
|
|
};
|
|
|
|
const 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 ( outputArray instanceof Uint8Array === false ) { return; }
|
|
if ( textDecoder === undefined ) {
|
|
textDecoder = new TextDecoder();
|
|
}
|
|
let value = textDecoder.decode(outputArray);
|
|
//console.info(
|
|
// 'uBO: [%s] decompressed %d KB => %d KB (%s%%) in %s ms',
|
|
// key,
|
|
// inputArray.byteLength >>> 10,
|
|
// outputSize >>> 10,
|
|
// (inputArray.byteLength / outputSize * 100).toFixed(0),
|
|
// (window.performance.now() - t0).toFixed(1)
|
|
//);
|
|
return value;
|
|
};
|
|
|
|
return {
|
|
encode: function(key, dataIn) {
|
|
if ( typeof dataIn !== 'string' || dataIn.length < 4096 ) {
|
|
return Promise.resolve({ key, data: dataIn });
|
|
}
|
|
ttlManage(1);
|
|
return init().then(( ) => {
|
|
ttlManage(-1);
|
|
let dataOut = encodeValue(key, dataIn) || dataIn;
|
|
if ( dataOut instanceof Uint8Array ) {
|
|
dataOut = new Blob([ dataOut ]);
|
|
}
|
|
return { key, data: dataOut || dataIn };
|
|
});
|
|
},
|
|
decode: function(key, dataIn) {
|
|
if ( dataIn instanceof Blob === false ) {
|
|
return Promise.resolve({ key, data: dataIn });
|
|
}
|
|
ttlManage(1);
|
|
return Promise.all([
|
|
init(),
|
|
uint8ArrayFromBlob(key, dataIn)
|
|
]).then(results => {
|
|
ttlManage(-1);
|
|
let result = results[1];
|
|
return {
|
|
key: result.key,
|
|
data: decodeValue(result.key, result.data) || result.data
|
|
};
|
|
});
|
|
},
|
|
relinquish: function() {
|
|
ttlDelay = 1;
|
|
ttlManage(0);
|
|
},
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
})(); // <<<< End of private namespace
|