diff --git a/.jshintrc b/.jshintrc
index b0fe596b30a66..b82e4d8f84c33 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -7,6 +7,7 @@
"browser": false, // global variable in Firefox, Edge
"chrome": false, // global variable in Chromium, Chrome, Opera
"Components": false, // global variable in Firefox
+ "log": false,
"safari": false,
"self": false,
"vAPI": false,
diff --git a/platform/chromium/vapi-common.js b/platform/chromium/vapi-common.js
index 8e577f441af21..0454086d3e7b1 100644
--- a/platform/chromium/vapi-common.js
+++ b/platform/chromium/vapi-common.js
@@ -30,6 +30,10 @@
/******************************************************************************/
+vAPI.T0 = Date.now();
+
+/******************************************************************************/
+
vAPI.setTimeout = vAPI.setTimeout || self.setTimeout.bind(self);
/******************************************************************************/
diff --git a/src/background.html b/src/background.html
index 104b941319556..484d559440c77 100644
--- a/src/background.html
+++ b/src/background.html
@@ -5,6 +5,7 @@
uBlock Origin
+
diff --git a/src/js/assets.js b/src/js/assets.js
index a1a9e11570257..3ccc7f8a5e236 100644
--- a/src/js/assets.js
+++ b/src/js/assets.js
@@ -449,26 +449,22 @@ const assetCacheRegistryStartTime = Date.now();
let assetCacheRegistryPromise;
let assetCacheRegistry = {};
-const getAssetCacheRegistry = function(callback) {
+const getAssetCacheRegistry = function() {
if ( assetCacheRegistryPromise === undefined ) {
assetCacheRegistryPromise = new Promise(resolve => {
- // start of executor
- µBlock.cacheStorage.get('assetCacheRegistry', bin => {
- if (
- bin instanceof Object &&
- bin.assetCacheRegistry instanceof Object
- ) {
- assetCacheRegistry = bin.assetCacheRegistry;
- }
- resolve();
- });
- // end of executor
+ µBlock.cacheStorage.get('assetCacheRegistry', bin => {
+ if (
+ bin instanceof Object &&
+ bin.assetCacheRegistry instanceof Object
+ ) {
+ assetCacheRegistry = bin.assetCacheRegistry;
+ }
+ resolve();
+ });
});
}
- assetCacheRegistryPromise.then(( ) => {
- callback(assetCacheRegistry);
- });
+ return assetCacheRegistryPromise.then(( ) => assetCacheRegistry);
};
const saveAssetCacheRegistry = (function() {
@@ -513,11 +509,9 @@ const assetCacheRead = function(assetKey, callback) {
reportBack(bin[internalKey]);
};
- let onReady = function() {
+ getAssetCacheRegistry().then(( ) => {
µBlock.cacheStorage.get(internalKey, onAssetRead);
- };
-
- getAssetCacheRegistry(onReady);
+ });
};
const assetCacheWrite = function(assetKey, details, callback) {
@@ -542,7 +536,18 @@ const assetCacheWrite = function(assetKey, details, callback) {
if ( details instanceof Object && typeof details.url === 'string' ) {
entry.remoteURL = details.url;
}
- µBlock.cacheStorage.set({ assetCacheRegistry, [internalKey]: content });
+ µBlock.cacheStorage.set(
+ { [internalKey]: content },
+ details => {
+ if (
+ details instanceof Object &&
+ typeof details.bytesInUse === 'number'
+ ) {
+ entry.byteLength = details.bytesInUse;
+ }
+ saveAssetCacheRegistry(true);
+ }
+ );
const result = { assetKey, content };
if ( typeof callback === 'function' ) {
callback(result);
@@ -550,14 +555,16 @@ const assetCacheWrite = function(assetKey, details, callback) {
// https://github.com/uBlockOrigin/uBlock-issues/issues/248
fireNotification('after-asset-updated', result);
};
- getAssetCacheRegistry(onReady);
+
+ getAssetCacheRegistry().then(( ) => {
+ µBlock.cacheStorage.get(internalKey, onReady);
+ });
};
const assetCacheRemove = function(pattern, callback) {
- const onReady = function() {
- const cacheDict = assetCacheRegistry,
- removedEntries = [],
- removedContent = [];
+ getAssetCacheRegistry().then(cacheDict => {
+ const removedEntries = [];
+ const removedContent = [];
for ( const assetKey in cacheDict ) {
if ( pattern instanceof RegExp && !pattern.test(assetKey) ) {
continue;
@@ -582,14 +589,15 @@ const assetCacheRemove = function(pattern, callback) {
{ assetKey: removedEntries[i] }
);
}
- };
-
- getAssetCacheRegistry(onReady);
+ });
};
const assetCacheMarkAsDirty = function(pattern, exclude, callback) {
- const onReady = function() {
- const cacheDict = assetCacheRegistry;
+ if ( typeof exclude === 'function' ) {
+ callback = exclude;
+ exclude = undefined;
+ }
+ getAssetCacheRegistry().then(cacheDict => {
let mustSave = false;
for ( const assetKey in cacheDict ) {
if ( pattern instanceof RegExp ) {
@@ -617,12 +625,7 @@ const assetCacheMarkAsDirty = function(pattern, exclude, callback) {
if ( typeof callback === 'function' ) {
callback();
}
- };
- if ( typeof exclude === 'function' ) {
- callback = exclude;
- exclude = undefined;
- }
- getAssetCacheRegistry(onReady);
+ });
};
/******************************************************************************/
@@ -642,12 +645,12 @@ const stringIsNotEmpty = function(s) {
**/
-var readUserAsset = function(assetKey, callback) {
- var reportBack = function(content) {
+const readUserAsset = function(assetKey, callback) {
+ const reportBack = function(content) {
callback({ assetKey: assetKey, content: content });
};
- var onLoaded = function(bin) {
+ const onLoaded = function(bin) {
if ( !bin ) { return reportBack(''); }
var content = '';
if ( typeof bin['cached_asset_content://assets/user/filters.txt'] === 'string' ) {
@@ -671,7 +674,7 @@ var readUserAsset = function(assetKey, callback) {
}
return reportBack(content);
};
- var toRead = assetKey;
+ let toRead = assetKey;
if ( assetKey === µBlock.userFiltersPath ) {
toRead = [
assetKey,
@@ -682,7 +685,7 @@ var readUserAsset = function(assetKey, callback) {
vAPI.storage.get(toRead, onLoaded);
};
-var saveUserAsset = function(assetKey, content, callback) {
+const saveUserAsset = function(assetKey, content, callback) {
var bin = {};
bin[assetKey] = content;
// TODO(seamless migration):
@@ -711,27 +714,33 @@ api.get = function(assetKey, options, callback) {
callback = noopfunc;
}
+ return new Promise(resolve => {
+ // start of executor
if ( assetKey === µBlock.userFiltersPath ) {
- readUserAsset(assetKey, callback);
+ readUserAsset(assetKey, details => {
+ callback(details);
+ resolve(details);
+ });
return;
}
- var assetDetails = {},
+ let assetDetails = {},
contentURLs,
contentURL;
- var reportBack = function(content, err) {
- var details = { assetKey: assetKey, content: content };
+ const reportBack = function(content, err) {
+ const details = { assetKey: assetKey, content: content };
if ( err ) {
details.error = assetDetails.lastError = err;
} else {
assetDetails.lastError = undefined;
}
callback(details);
+ resolve(details);
};
- var onContentNotLoaded = function() {
- var isExternal;
+ const onContentNotLoaded = function() {
+ let isExternal;
while ( (contentURL = contentURLs.shift()) ) {
isExternal = reIsExternalPath.test(contentURL);
if ( isExternal === false || assetDetails.hasLocalURL !== true ) {
@@ -748,7 +757,7 @@ api.get = function(assetKey, options, callback) {
}
};
- var onContentLoaded = function(details) {
+ const onContentLoaded = function(details) {
if ( stringIsNotEmpty(details.content) === false ) {
onContentNotLoaded();
return;
@@ -762,7 +771,7 @@ api.get = function(assetKey, options, callback) {
reportBack(details.content);
};
- var onCachedContentLoaded = function(details) {
+ const onCachedContentLoaded = function(details) {
if ( details.content !== '' ) {
return reportBack(details.content);
}
@@ -780,11 +789,13 @@ api.get = function(assetKey, options, callback) {
};
assetCacheRead(assetKey, onCachedContentLoaded);
+ // end of executor
+ });
};
/******************************************************************************/
-var getRemote = function(assetKey, callback) {
+const getRemote = function(assetKey, callback) {
var assetDetails = {},
contentURLs,
contentURL;
@@ -852,10 +863,19 @@ var getRemote = function(assetKey, callback) {
/******************************************************************************/
api.put = function(assetKey, content, callback) {
- if ( reIsUserAsset.test(assetKey) ) {
- return saveUserAsset(assetKey, content, callback);
- }
- assetCacheWrite(assetKey, content, callback);
+ return new Promise(resolve => {
+ const onDone = function(details) {
+ if ( typeof callback === 'function' ) {
+ callback(details);
+ }
+ resolve(details);
+ };
+ if ( reIsUserAsset.test(assetKey) ) {
+ saveUserAsset(assetKey, content, onDone);
+ } else {
+ assetCacheWrite(assetKey, content, onDone);
+ }
+ });
};
/******************************************************************************/
@@ -895,7 +915,7 @@ api.metadata = function(callback) {
if ( cacheRegistryReady ) { onReady(); }
});
- getAssetCacheRegistry(function() {
+ getAssetCacheRegistry().then(( ) => {
cacheRegistryReady = true;
if ( assetRegistryReady ) { onReady(); }
});
@@ -903,6 +923,19 @@ api.metadata = function(callback) {
/******************************************************************************/
+api.getBytesInUse = function() {
+ return getAssetCacheRegistry().then(cacheDict => {
+ let bytesUsed = 0;
+ for ( const assetKey in cacheDict ) {
+ if ( cacheDict.hasOwnProperty(assetKey) === false ) { continue; }
+ bytesUsed += cacheDict[assetKey].byteLength || 0;
+ }
+ return bytesUsed;
+ });
+};
+
+/******************************************************************************/
+
api.purge = assetCacheMarkAsDirty;
api.remove = function(pattern, callback) {
@@ -1013,7 +1046,7 @@ var updateNext = function() {
updateOne();
});
- getAssetCacheRegistry(function(dict) {
+ getAssetCacheRegistry().then(dict => {
cacheDict = dict;
if ( !assetDict ) { return; }
updateOne();
diff --git a/src/js/background.js b/src/js/background.js
index 37dff9d9f7ac1..52cbb67a3e6bc 100644
--- a/src/js/background.js
+++ b/src/js/background.js
@@ -46,6 +46,7 @@ const µBlock = (function() { // jshint ignore:line
cacheStorageAPI: 'unset',
cacheStorageCompression: true,
cacheControlForFirefox1376932: 'no-cache, no-store, must-revalidate',
+ consoleLogLevel: 'unset',
debugScriptlets: false,
disableWebAssembly: false,
ignoreRedirectFilters: false,
@@ -53,6 +54,7 @@ const µBlock = (function() { // jshint ignore:line
manualUpdateAssetFetchPeriod: 500,
popupFontSize: 'unset',
requestJournalProcessPeriod: 1000,
+ selfieAfter: 11,
strictBlockingBypassDuration: 120,
suspendTabsUntilReady: false,
userResourcesLocation: 'unset'
@@ -95,13 +97,13 @@ const µBlock = (function() { // jshint ignore:line
hiddenSettingsDefault: hiddenSettingsDefault,
hiddenSettings: (function() {
- let out = Object.assign({}, hiddenSettingsDefault),
+ const out = Object.assign({}, hiddenSettingsDefault),
json = vAPI.localStorage.getItem('immediateHiddenSettings');
if ( typeof json === 'string' ) {
try {
- let o = JSON.parse(json);
+ const o = JSON.parse(json);
if ( o instanceof Object ) {
- for ( let k in o ) {
+ for ( const k in o ) {
if ( out.hasOwnProperty(k) ) {
out[k] = o[k];
}
@@ -111,8 +113,6 @@ const µBlock = (function() { // jshint ignore:line
catch(ex) {
}
}
- // Remove once 1.15.12+ is widespread.
- vAPI.localStorage.removeItem('hiddenSettings');
return out;
})(),
@@ -138,7 +138,7 @@ const µBlock = (function() { // jshint ignore:line
// Read-only
systemSettings: {
compiledMagic: 6, // Increase when compiled format changes
- selfieMagic: 7 // Increase when selfie format changes
+ selfieMagic: 8 // Increase when selfie format changes
},
restoreBackupSettings: {
@@ -161,8 +161,6 @@ const µBlock = (function() { // jshint ignore:line
selectedFilterLists: [],
availableFilterLists: {},
- selfieAfter: 17 * oneMinute,
-
pageStores: new Map(),
pageStoresToken: 0,
diff --git a/src/js/cachestorage.js b/src/js/cachestorage.js
index 9b5a0035f0e2d..47771fe525bc5 100644
--- a/src/js/cachestorage.js
+++ b/src/js/cachestorage.js
@@ -326,17 +326,27 @@
if ( typeof callback !== 'function' ) {
callback = noopfn;
}
- let keys = Object.keys(keyvalStore);
+ const keys = Object.keys(keyvalStore);
if ( keys.length === 0 ) { return callback(); }
- let promises = [ getDb() ];
- let entries = [];
- let dontCompress = µBlock.hiddenSettings.cacheStorageCompression !== true;
- let handleEncodingResult = result => {
+ const promises = [ getDb() ];
+ const entries = [];
+ const dontCompress = µBlock.hiddenSettings.cacheStorageCompression !== true;
+ let bytesInUse = 0;
+ const handleEncodingResult = result => {
+ if ( typeof result.data === 'string' ) {
+ bytesInUse += result.data.length;
+ } else if ( result.data instanceof Blob ) {
+ bytesInUse += result.data.size;
+ }
entries.push({ key: result.key, value: result.data });
};
- for ( let key of keys ) {
- let data = keyvalStore[key];
- if ( typeof data !== 'string' || dontCompress ) {
+ for ( const key of keys ) {
+ const data = keyvalStore[key];
+ const isString = typeof data === 'string';
+ if ( isString === false || dontCompress ) {
+ if ( isString ) {
+ bytesInUse += data.length;
+ }
entries.push({ key, value: data });
continue;
}
@@ -346,20 +356,20 @@
}
Promise.all(promises).then(( ) => {
if ( !db ) { return callback(); }
- let finish = ( ) => {
+ const finish = ( ) => {
dbBytesInUse = undefined;
if ( callback === undefined ) { return; }
let cb = callback;
callback = undefined;
- cb();
+ cb({ bytesInUse });
};
try {
- let transaction = db.transaction(STORAGE_NAME, 'readwrite');
+ const transaction = db.transaction(STORAGE_NAME, 'readwrite');
transaction.oncomplete =
transaction.onerror =
transaction.onabort = finish;
- let table = transaction.objectStore(STORAGE_NAME);
- for ( let entry of entries ) {
+ const table = transaction.objectStore(STORAGE_NAME);
+ for ( const entry of entries ) {
table.put(entry);
}
} catch (ex) {
diff --git a/src/js/console.js b/src/js/console.js
new file mode 100644
index 0000000000000..9e7fafb5c08b9
--- /dev/null
+++ b/src/js/console.js
@@ -0,0 +1,34 @@
+/*******************************************************************************
+
+ uBlock Origin - a browser extension to block requests.
+ Copyright (C) 2019-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+'use strict';
+
+self.log = (function() {
+ const noopFunc = function() {};
+ const info = function(s) { console.log(`[uBO] ${s}`); };
+ return {
+ get verbosity( ) { return; },
+ set verbosity(level) {
+ this.info = console.info = level === 'info' ? info : noopFunc;
+ },
+ info,
+ };
+})();
diff --git a/src/js/hntrie.js b/src/js/hntrie.js
index 2ad411618c833..607d8ef8aee4b 100644
--- a/src/js/hntrie.js
+++ b/src/js/hntrie.js
@@ -355,7 +355,13 @@ HNTrieContainer.prototype = {
return trieRef;
},
- serialize: function() {
+ serialize: function(encoder) {
+ if ( encoder instanceof Object ) {
+ return encoder.encode(
+ this.buf32.buffer,
+ this.buf32[HNTRIE_CHAR1_SLOT]
+ );
+ }
return Array.from(
new Uint32Array(
this.buf32.buffer,
@@ -365,23 +371,29 @@ HNTrieContainer.prototype = {
);
},
- unserialize: function(selfie) {
- const len = (selfie.length << 2) + HNTRIE_PAGE_SIZE-1 & ~(HNTRIE_PAGE_SIZE-1);
+ unserialize: function(selfie, decoder) {
+ const shouldDecode = typeof selfie === 'string';
+ let byteLength = shouldDecode
+ ? decoder.decodeSize(selfie)
+ : selfie.length << 2;
+ byteLength = byteLength + HNTRIE_PAGE_SIZE-1 & ~(HNTRIE_PAGE_SIZE-1);
if ( this.wasmMemory !== null ) {
const pageCountBefore = this.buf.length >>> 16;
- const pageCountAfter = len >>> 16;
+ const pageCountAfter = byteLength >>> 16;
if ( pageCountAfter > pageCountBefore ) {
this.wasmMemory.grow(pageCountAfter - pageCountBefore);
this.buf = new Uint8Array(this.wasmMemory.buffer);
this.buf32 = new Uint32Array(this.buf.buffer);
}
+ } else if ( byteLength > this.buf.length ) {
+ this.buf = new Uint8Array(byteLength);
+ this.buf32 = new Uint32Array(this.buf.buffer);
+ }
+ if ( shouldDecode ) {
+ decoder.decode(selfie, this.buf.buffer);
} else {
- if ( len > this.buf.length ) {
- this.buf = new Uint8Array(len);
- this.buf32 = new Uint32Array(this.buf.buffer);
- }
+ this.buf32.set(selfie);
}
- this.buf32.set(selfie);
this.needle = '';
},
@@ -684,6 +696,6 @@ HNTrieContainer.prototype.HNTrieRef.prototype = {
WebAssembly.compileStreaming
).catch(reason => {
HNTrieContainer.wasmModulePromise = null;
- console.info(reason);
+ log.info(reason);
});
})();
diff --git a/src/js/redirect-engine.js b/src/js/redirect-engine.js
index 2faf39cd5b6a6..419361216ed76 100644
--- a/src/js/redirect-engine.js
+++ b/src/js/redirect-engine.js
@@ -29,12 +29,12 @@
/******************************************************************************/
const warResolve = (function() {
- var warPairs = [];
+ let warPairs = [];
- var onPairsReady = function() {
- var reng = µBlock.redirectEngine;
- for ( var i = 0; i < warPairs.length; i += 2 ) {
- var resource = reng.resources.get(warPairs[i+0]);
+ const onPairsReady = function() {
+ const reng = µBlock.redirectEngine;
+ for ( let i = 0; i < warPairs.length; i += 2 ) {
+ const resource = reng.resources.get(warPairs[i+0]);
if ( resource === undefined ) { continue; }
resource.warURL = vAPI.getURL(
'/web_accessible_resources/' + warPairs[i+1]
@@ -48,15 +48,15 @@ const warResolve = (function() {
return onPairsReady();
}
- var onPairsLoaded = function(details) {
- var marker = '>>>>>';
- var pos = details.content.indexOf(marker);
+ const onPairsLoaded = function(details) {
+ const marker = '>>>>>';
+ const pos = details.content.indexOf(marker);
if ( pos === -1 ) { return; }
- var pairs = details.content.slice(pos + marker.length)
+ const pairs = details.content.slice(pos + marker.length)
.trim()
.split('\n');
if ( (pairs.length & 1) !== 0 ) { return; }
- for ( var i = 0; i < pairs.length; i++ ) {
+ for ( let i = 0; i < pairs.length; i++ ) {
pairs[i] = pairs[i].trim();
}
warPairs = pairs;
@@ -64,7 +64,7 @@ const warResolve = (function() {
};
µBlock.assets.fetchText(
- '/web_accessible_resources/imported.txt?secret=' + vAPI.warSecret,
+ `/web_accessible_resources/imported.txt?secret=${vAPI.warSecret}`,
onPairsLoaded
);
};
@@ -374,18 +374,17 @@ RedirectEngine.prototype.supportedTypes = new Map([
/******************************************************************************/
-RedirectEngine.prototype.toSelfie = function() {
+RedirectEngine.prototype.toSelfie = function(path) {
// Because rules may contains RegExp instances, we need to manually
// convert it to a serializable format. The serialized format must be
// suitable to be used as an argument to the Map() constructor.
- var rules = [],
- rule, entries, i, entry;
- for ( var item of this.rules ) {
- rule = [ item[0], [] ];
- entries = item[1];
- i = entries.length;
+ const rules = [];
+ for ( const item of this.rules ) {
+ const rule = [ item[0], [] ];
+ const entries = item[1];
+ let i = entries.length;
while ( i-- ) {
- entry = entries[i];
+ const entry = entries[i];
rule[1].push({
tok: entry.tok,
pat: entry.pat instanceof RegExp ? entry.pat.source : entry.pat
@@ -393,23 +392,34 @@ RedirectEngine.prototype.toSelfie = function() {
}
rules.push(rule);
}
- return {
- rules: rules,
- ruleTypes: Array.from(this.ruleTypes),
- ruleSources: Array.from(this.ruleSources),
- ruleDestinations: Array.from(this.ruleDestinations)
- };
+ return µBlock.assets.put(
+ `${path}/main`,
+ JSON.stringify({
+ rules: rules,
+ ruleTypes: Array.from(this.ruleTypes),
+ ruleSources: Array.from(this.ruleSources),
+ ruleDestinations: Array.from(this.ruleDestinations)
+ })
+ );
};
/******************************************************************************/
-RedirectEngine.prototype.fromSelfie = function(selfie) {
- this.rules = new Map(selfie.rules);
- this.ruleTypes = new Set(selfie.ruleTypes);
- this.ruleSources = new Set(selfie.ruleSources);
- this.ruleDestinations = new Set(selfie.ruleDestinations);
- this.modifyTime = Date.now();
- return true;
+RedirectEngine.prototype.fromSelfie = function(path) {
+ return µBlock.assets.get(`${path}/main`).then(details => {
+ let selfie;
+ try {
+ selfie = JSON.parse(details.content);
+ } catch (ex) {
+ }
+ if ( selfie instanceof Object === false ) { return false; }
+ this.rules = new Map(selfie.rules);
+ this.ruleTypes = new Set(selfie.ruleTypes);
+ this.ruleSources = new Set(selfie.ruleSources);
+ this.ruleDestinations = new Set(selfie.ruleDestinations);
+ this.modifyTime = Date.now();
+ return true;
+ });
};
/******************************************************************************/
@@ -494,41 +504,46 @@ RedirectEngine.prototype.resourcesFromString = function(text) {
/******************************************************************************/
-let resourcesSelfieVersion = 3;
+const resourcesSelfieVersion = 3;
RedirectEngine.prototype.selfieFromResources = function() {
- let selfie = {
- version: resourcesSelfieVersion,
- resources: Array.from(this.resources)
- };
- µBlock.cacheStorage.set({ resourcesSelfie: JSON.stringify(selfie) });
+ µBlock.assets.put(
+ 'compiled/redirectEngine/resources',
+ JSON.stringify({
+ version: resourcesSelfieVersion,
+ resources: Array.from(this.resources)
+ })
+ );
};
-RedirectEngine.prototype.resourcesFromSelfie = function(callback) {
- µBlock.cacheStorage.get('resourcesSelfie', bin => {
- let selfie = bin && bin.resourcesSelfie;
- if ( typeof selfie === 'string' ) {
- try {
- selfie = JSON.parse(selfie);
- } catch(ex) {
- }
+RedirectEngine.prototype.resourcesFromSelfie = function() {
+ return µBlock.assets.get(
+ 'compiled/redirectEngine/resources'
+ ).then(details => {
+ let selfie;
+ try {
+ selfie = JSON.parse(details.content);
+ } catch(ex) {
}
if (
selfie instanceof Object === false ||
selfie.version !== resourcesSelfieVersion ||
Array.isArray(selfie.resources) === false
) {
- return callback(false);
+ return false;
}
this.resources = new Map();
- for ( let entry of selfie.resources ) {
- this.resources.set(entry[0], RedirectEntry.fromSelfie(entry[1]));
+ for ( const [ token, entry ] of selfie.resources ) {
+ this.resources.set(token, RedirectEntry.fromSelfie(entry));
}
- callback(true);
+ return true;
});
};
RedirectEngine.prototype.invalidateResourcesSelfie = function() {
+ µBlock.assets.remove('compiled/redirectEngine/resources');
+
+ // TODO: obsolete, remove eventually
µBlock.cacheStorage.remove('resourcesSelfie');
};
diff --git a/src/js/start.js b/src/js/start.js
index ad7ba5e739ea9..26cad13937f04 100644
--- a/src/js/start.js
+++ b/src/js/start.js
@@ -81,6 +81,8 @@ var onAllReady = function() {
µb.contextMenu.update(null);
µb.firstInstall = false;
+
+ log.info(`All ready ${Date.now()-vAPI.T0} ms after launch`);
};
/******************************************************************************/
@@ -137,22 +139,29 @@ let initializeTabs = function() {
// Filtering engines dependencies:
// - PSL
-var onPSLReady = function() {
- µb.selfieManager.load(function(valid) {
+const onPSLReady = function() {
+ log.info(`PSL ready ${Date.now()-vAPI.T0} ms after launch`);
+
+ µb.selfieManager.load().then(valid => {
if ( valid === true ) {
- return onAllReady();
+ log.info(`Selfie ready ${Date.now()-vAPI.T0} ms after launch`);
+ onAllReady();
+ return;
}
- µb.loadFilterLists(onAllReady);
+ µb.loadFilterLists(( ) => {
+ log.info(`Filter lists ready ${Date.now()-vAPI.T0} ms after launch`);
+ onAllReady();
+ });
});
};
/******************************************************************************/
-var onCommandShortcutsReady = function(commandShortcuts) {
+const onCommandShortcutsReady = function(commandShortcuts) {
if ( Array.isArray(commandShortcuts) === false ) { return; }
µb.commandShortcuts = new Map(commandShortcuts);
if ( µb.canUpdateShortcuts === false ) { return; }
- for ( let entry of commandShortcuts ) {
+ for ( const entry of commandShortcuts ) {
vAPI.commands.update({ name: entry[0], shortcut: entry[1] });
}
};
@@ -161,7 +170,7 @@ var onCommandShortcutsReady = function(commandShortcuts) {
// To bring older versions up to date
-var onVersionReady = function(lastVersion) {
+const onVersionReady = function(lastVersion) {
if ( lastVersion === vAPI.app.version ) { return; }
// Since AMO does not allow updating resources.txt, force a reload when a
@@ -176,7 +185,7 @@ var onVersionReady = function(lastVersion) {
// If unused, just comment out for when we need to compare versions in the
// future.
- let intFromVersion = function(s) {
+ const intFromVersion = function(s) {
let parts = s.match(/(?:^|\.|b|rc)\d+/g);
if ( parts === null ) { return 0; }
let vint = 0;
@@ -223,7 +232,7 @@ var onVersionReady = function(lastVersion) {
// Whitelist parser needs PSL to be ready.
// gorhill 2014-12-15: not anymore
-var onNetWhitelistReady = function(netWhitelistRaw) {
+const onNetWhitelistReady = function(netWhitelistRaw) {
µb.netWhitelist = µb.whitelistFromString(netWhitelistRaw);
µb.netWhitelistModifyTime = Date.now();
};
@@ -232,8 +241,10 @@ var onNetWhitelistReady = function(netWhitelistRaw) {
// User settings are in memory
-var onUserSettingsReady = function(fetched) {
- var userSettings = µb.userSettings;
+const onUserSettingsReady = function(fetched) {
+ log.info(`User settings ready ${Date.now()-vAPI.T0} ms after launch`);
+
+ const userSettings = µb.userSettings;
fromFetch(userSettings, fetched);
@@ -264,7 +275,7 @@ var onUserSettingsReady = function(fetched) {
// Housekeeping, as per system setting changes
-var onSystemSettingsReady = function(fetched) {
+const onSystemSettingsReady = function(fetched) {
var mustSaveSystemSettings = false;
if ( fetched.compiledMagic !== µb.systemSettings.compiledMagic ) {
µb.assets.remove(/^compiled\//);
@@ -282,7 +293,9 @@ var onSystemSettingsReady = function(fetched) {
/******************************************************************************/
-var onFirstFetchReady = function(fetched) {
+const onFirstFetchReady = function(fetched) {
+ log.info(`First fetch ready ${Date.now()-vAPI.T0} ms after launch`);
+
// https://github.com/gorhill/uBlock/issues/747
µb.firstInstall = fetched.version === '0.0.0.0';
@@ -295,10 +308,7 @@ var onFirstFetchReady = function(fetched) {
onVersionReady(fetched.version);
onCommandShortcutsReady(fetched.commandShortcuts);
- Promise.all([
- µb.loadPublicSuffixList(),
- µb.staticNetFilteringEngine.readyToUse()
- ]).then(( ) => {
+ µb.loadPublicSuffixList().then(( ) => {
onPSLReady();
});
µb.loadRedirectResources();
@@ -306,31 +316,27 @@ var onFirstFetchReady = function(fetched) {
/******************************************************************************/
-var toFetch = function(from, fetched) {
- for ( var k in from ) {
- if ( from.hasOwnProperty(k) === false ) {
- continue;
- }
+const toFetch = function(from, fetched) {
+ for ( const k in from ) {
+ if ( from.hasOwnProperty(k) === false ) { continue; }
fetched[k] = from[k];
}
};
-var fromFetch = function(to, fetched) {
- for ( var k in to ) {
- if ( to.hasOwnProperty(k) === false ) {
- continue;
- }
- if ( fetched.hasOwnProperty(k) === false ) {
- continue;
- }
+const fromFetch = function(to, fetched) {
+ for ( const k in to ) {
+ if ( to.hasOwnProperty(k) === false ) { continue; }
+ if ( fetched.hasOwnProperty(k) === false ) { continue; }
to[k] = fetched[k];
}
};
/******************************************************************************/
-var onSelectedFilterListsLoaded = function() {
- var fetchableProps = {
+const onSelectedFilterListsLoaded = function() {
+ log.info(`List selection ready ${Date.now()-vAPI.T0} ms after launch`);
+
+ const fetchableProps = {
'commandShortcuts': [],
'compiledMagic': 0,
'dynamicFilteringString': [
@@ -371,7 +377,8 @@ var onSelectedFilterListsLoaded = function() {
// compatibility, this means a special asynchronous call to load selected
// filter lists.
-var onAdminSettingsRestored = function() {
+const onAdminSettingsRestored = function() {
+ log.info(`Admin settings ready ${Date.now()-vAPI.T0} ms after launch`);
µb.loadSelectedFilterLists(onSelectedFilterListsLoaded);
};
diff --git a/src/js/static-ext-filtering.js b/src/js/static-ext-filtering.js
index 135cb0980d9c6..8b2f533ef0415 100644
--- a/src/js/static-ext-filtering.js
+++ b/src/js/static-ext-filtering.js
@@ -821,18 +821,30 @@
µb.htmlFilteringEngine.fromCompiledContent(reader, options);
};
- api.toSelfie = function() {
- return {
- cosmetic: µb.cosmeticFilteringEngine.toSelfie(),
- scriptlets: µb.scriptletFilteringEngine.toSelfie(),
- html: µb.htmlFilteringEngine.toSelfie()
- };
+ api.toSelfie = function(path) {
+ return µBlock.assets.put(
+ `${path}/main`,
+ JSON.stringify({
+ cosmetic: µb.cosmeticFilteringEngine.toSelfie(),
+ scriptlets: µb.scriptletFilteringEngine.toSelfie(),
+ html: µb.htmlFilteringEngine.toSelfie()
+ })
+ );
};
- api.fromSelfie = function(selfie) {
- µb.cosmeticFilteringEngine.fromSelfie(selfie.cosmetic);
- µb.scriptletFilteringEngine.fromSelfie(selfie.scriptlets);
- µb.htmlFilteringEngine.fromSelfie(selfie.html);
+ api.fromSelfie = function(path) {
+ return µBlock.assets.get(`${path}/main`).then(details => {
+ let selfie;
+ try {
+ selfie = JSON.parse(details.content);
+ } catch (ex) {
+ }
+ if ( selfie instanceof Object === false ) { return false; }
+ µb.cosmeticFilteringEngine.fromSelfie(selfie.cosmetic);
+ µb.scriptletFilteringEngine.fromSelfie(selfie.scriptlets);
+ µb.htmlFilteringEngine.fromSelfie(selfie.html);
+ return true;
+ });
};
return api;
diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js
index 59518f056b3e2..2d2874c9c1c60 100644
--- a/src/js/static-net-filtering.js
+++ b/src/js/static-net-filtering.js
@@ -2105,21 +2105,21 @@ FilterContainer.prototype.readyToUse = function() {
/******************************************************************************/
-FilterContainer.prototype.toSelfie = function() {
- let categoriesToSelfie = function(categoryMap) {
- let selfie = [];
- for ( let categoryEntry of categoryMap ) {
- let tokenEntries = [];
- for ( let tokenEntry of categoryEntry[1] ) {
- tokenEntries.push([ tokenEntry[0], tokenEntry[1].compile() ]);
+FilterContainer.prototype.toSelfie = function(path) {
+ const categoriesToSelfie = function(categoryMap) {
+ const selfie = [];
+ for ( const [ catbits, bucket ] of categoryMap ) {
+ const tokenEntries = [];
+ for ( const [ token, filter ] of bucket ) {
+ tokenEntries.push([ token, filter.compile() ]);
}
- selfie.push([ categoryEntry[0], tokenEntries ]);
+ selfie.push([ catbits, tokenEntries ]);
}
return selfie;
};
- let dataFiltersToSelfie = function(dataFilters) {
- let selfie = [];
+ const dataFiltersToSelfie = function(dataFilters) {
+ const selfie = [];
for ( let entry of dataFilters.values() ) {
do {
selfie.push(entry.compile());
@@ -2129,47 +2129,72 @@ FilterContainer.prototype.toSelfie = function() {
return selfie;
};
- return {
- processedFilterCount: this.processedFilterCount,
- acceptedCount: this.acceptedCount,
- rejectedCount: this.rejectedCount,
- allowFilterCount: this.allowFilterCount,
- blockFilterCount: this.blockFilterCount,
- discardedCount: this.discardedCount,
- trieContainer: FilterHostnameDict.trieContainer.serialize(),
- categories: categoriesToSelfie(this.categories),
- dataFilters: dataFiltersToSelfie(this.dataFilters)
- };
+ return Promise.all([
+ µBlock.assets.put(
+ `${path}/trieContainer`,
+ FilterHostnameDict.trieContainer.serialize(µBlock.base128)
+ ),
+ µBlock.assets.put(
+ `${path}/main`,
+ JSON.stringify({
+ processedFilterCount: this.processedFilterCount,
+ acceptedCount: this.acceptedCount,
+ rejectedCount: this.rejectedCount,
+ allowFilterCount: this.allowFilterCount,
+ blockFilterCount: this.blockFilterCount,
+ discardedCount: this.discardedCount,
+ categories: categoriesToSelfie(this.categories),
+ dataFilters: dataFiltersToSelfie(this.dataFilters),
+ })
+ )
+ ]);
};
/******************************************************************************/
-FilterContainer.prototype.fromSelfie = function(selfie) {
- this.frozen = true;
- this.processedFilterCount = selfie.processedFilterCount;
- this.acceptedCount = selfie.acceptedCount;
- this.rejectedCount = selfie.rejectedCount;
- this.allowFilterCount = selfie.allowFilterCount;
- this.blockFilterCount = selfie.blockFilterCount;
- this.discardedCount = selfie.discardedCount;
- FilterHostnameDict.trieContainer.unserialize(selfie.trieContainer);
-
- for ( let categoryEntry of selfie.categories ) {
- let tokenMap = new Map();
- for ( let tokenEntry of categoryEntry[1] ) {
- tokenMap.set(tokenEntry[0], filterFromCompiledData(tokenEntry[1]));
- }
- this.categories.set(categoryEntry[0], tokenMap);
- }
-
- for ( let dataEntry of selfie.dataFilters ) {
- let entry = FilterDataHolderEntry.load(dataEntry);
- let bucket = this.dataFilters.get(entry.tokenHash);
- if ( bucket !== undefined ) {
- entry.next = bucket;
- }
- this.dataFilters.set(entry.tokenHash, entry);
- }
+FilterContainer.prototype.fromSelfie = function(path) {
+ return Promise.all([
+ µBlock.assets.get(`${path}/trieContainer`).then(details => {
+ FilterHostnameDict.trieContainer.unserialize(
+ details.content,
+ µBlock.base128
+ );
+ return true;
+ }),
+ µBlock.assets.get(`${path}/main`).then(details => {
+ let selfie;
+ try {
+ selfie = JSON.parse(details.content);
+ } catch (ex) {
+ }
+ if ( selfie instanceof Object === false ) { return false; }
+ this.frozen = true;
+ this.processedFilterCount = selfie.processedFilterCount;
+ this.acceptedCount = selfie.acceptedCount;
+ this.rejectedCount = selfie.rejectedCount;
+ this.allowFilterCount = selfie.allowFilterCount;
+ this.blockFilterCount = selfie.blockFilterCount;
+ this.discardedCount = selfie.discardedCount;
+ for ( const [ catbits, bucket ] of selfie.categories ) {
+ const tokenMap = new Map();
+ for ( const [ token, fdata ] of bucket ) {
+ tokenMap.set(token, filterFromCompiledData(fdata));
+ }
+ this.categories.set(catbits, tokenMap);
+ }
+ for ( const dataEntry of selfie.dataFilters ) {
+ const entry = FilterDataHolderEntry.load(dataEntry);
+ const bucket = this.dataFilters.get(entry.tokenHash);
+ if ( bucket !== undefined ) {
+ entry.next = bucket;
+ }
+ this.dataFilters.set(entry.tokenHash, entry);
+ }
+ return true;
+ }),
+ ]).then(results =>
+ results.reduce((acc, v) => acc && v, true)
+ );
};
/******************************************************************************/
diff --git a/src/js/storage.js b/src/js/storage.js
index bd281decc6f8f..9076216f3fe06 100644
--- a/src/js/storage.js
+++ b/src/js/storage.js
@@ -32,7 +32,7 @@
let bytesInUse;
let countdown = 0;
- let process = count => {
+ const process = count => {
if ( typeof count === 'number' ) {
if ( bytesInUse === undefined ) {
bytesInUse = 0;
@@ -50,12 +50,11 @@
countdown += 1;
vAPI.storage.getBytesInUse(null, process);
}
- if (
- this.cacheStorage !== vAPI.storage &&
- this.cacheStorage.getBytesInUse instanceof Function
- ) {
+ if ( this.cacheStorage !== vAPI.storage ) {
countdown += 1;
- this.cacheStorage.getBytesInUse(null, process);
+ this.assets.getBytesInUse().then(count => {
+ process(count);
+ });
}
if ( countdown === 0 ) {
callback();
@@ -94,10 +93,10 @@
µBlock.loadHiddenSettings = function() {
vAPI.storage.get('hiddenSettings', bin => {
if ( bin instanceof Object === false ) { return; }
- let hs = bin.hiddenSettings;
+ const hs = bin.hiddenSettings;
if ( hs instanceof Object ) {
- let hsDefault = this.hiddenSettingsDefault;
- for ( let key in hsDefault ) {
+ const hsDefault = this.hiddenSettingsDefault;
+ for ( const key in hsDefault ) {
if (
hsDefault.hasOwnProperty(key) &&
hs.hasOwnProperty(key) &&
@@ -110,6 +109,7 @@
if ( vAPI.localStorage.getItem('immediateHiddenSettings') === null ) {
this.saveImmediateHiddenSettings();
}
+ self.log.verbosity = this.hiddenSettings.consoleLogLevel;
});
};
@@ -118,8 +118,8 @@
// which were not modified by the user.
µBlock.saveHiddenSettings = function(callback) {
- let bin = { hiddenSettings: {} };
- for ( let prop in this.hiddenSettings ) {
+ const bin = { hiddenSettings: {} };
+ for ( const prop in this.hiddenSettings ) {
if (
this.hiddenSettings.hasOwnProperty(prop) &&
this.hiddenSettings[prop] !== this.hiddenSettingsDefault[prop]
@@ -129,6 +129,7 @@
}
vAPI.storage.set(bin, callback);
this.saveImmediateHiddenSettings();
+ self.log.verbosity = this.hiddenSettings.consoleLogLevel;
};
/******************************************************************************/
@@ -969,41 +970,41 @@
/******************************************************************************/
µBlock.loadRedirectResources = function(updatedContent) {
- var µb = this,
- content = '';
+ let content = '';
- var onDone = function() {
- µb.redirectEngine.resourcesFromString(content);
+ const onDone = ( ) => {
+ this.redirectEngine.resourcesFromString(content);
};
- var onUserResourcesLoaded = function(details) {
+ const onUserResourcesLoaded = details => {
if ( details.content !== '' ) {
content += '\n\n' + details.content;
}
onDone();
};
- var onResourcesLoaded = function(details) {
+ const onResourcesLoaded = details => {
if ( details.content !== '' ) {
content = details.content;
}
- if ( µb.hiddenSettings.userResourcesLocation === 'unset' ) {
+ if ( this.hiddenSettings.userResourcesLocation === 'unset' ) {
return onDone();
}
- µb.assets.fetchText(µb.hiddenSettings.userResourcesLocation, onUserResourcesLoaded);
+ this.assets.fetchText(
+ this.hiddenSettings.userResourcesLocation,
+ onUserResourcesLoaded
+ );
};
if ( typeof updatedContent === 'string' && updatedContent.length !== 0 ) {
return onResourcesLoaded({ content: updatedContent });
}
- var onSelfieReady = function(success) {
+ this.redirectEngine.resourcesFromSelfie().then(success => {
if ( success !== true ) {
- µb.assets.get('ublock-resources', onResourcesLoaded);
+ this.assets.get('ublock-resources', onResourcesLoaded);
}
- };
-
- µb.redirectEngine.resourcesFromSelfie(onSelfieReady);
+ });
};
/******************************************************************************/
@@ -1013,39 +1014,25 @@
publicSuffixList.enableWASM();
}
- return new Promise(resolve => {
- // start of executor
- this.assets.get('compiled/' + this.pslAssetKey, details => {
- let selfie;
- try {
- selfie = JSON.parse(details.content);
- } catch (ex) {
- }
- if (
- selfie instanceof Object &&
- publicSuffixList.fromSelfie(selfie)
- ) {
- resolve();
- return;
- }
- this.assets.get(this.pslAssetKey, details => {
+ return this.assets.get(
+ 'compiled/' + this.pslAssetKey
+ ).then(details =>
+ publicSuffixList.fromSelfie(details.content, µBlock.base128)
+ ).then(valid => {
+ if ( valid === true ) { return; }
+ return this.assets.get(this.pslAssetKey, details => {
if ( details.content !== '' ) {
this.compilePublicSuffixList(details.content);
}
- resolve();
});
});
- // end of executor
- });
};
-/******************************************************************************/
-
µBlock.compilePublicSuffixList = function(content) {
publicSuffixList.parse(content, punycode.toASCII);
this.assets.put(
'compiled/' + this.pslAssetKey,
- JSON.stringify(publicSuffixList.toSelfie())
+ publicSuffixList.toSelfie(µBlock.base128)
);
};
@@ -1056,60 +1043,76 @@
// some set time.
µBlock.selfieManager = (function() {
- let µb = µBlock;
- let timer = null;
+ const µb = µBlock;
+ let timer;
// As of 2018-05-31:
- // JSON.stringify-ing ourselves results in a better baseline
- // memory usage at selfie-load time. For some reasons.
-
- let create = function() {
- timer = null;
- let selfie = JSON.stringify({
- magic: µb.systemSettings.selfieMagic,
- availableFilterLists: µb.availableFilterLists,
- staticNetFilteringEngine: µb.staticNetFilteringEngine.toSelfie(),
- redirectEngine: µb.redirectEngine.toSelfie(),
- staticExtFilteringEngine: µb.staticExtFilteringEngine.toSelfie()
+ // JSON.stringify-ing ourselves results in a better baseline
+ // memory usage at selfie-load time. For some reasons.
+
+ const create = function() {
+ Promise.all([
+ µb.assets.put(
+ 'selfie/main',
+ JSON.stringify({
+ magic: µb.systemSettings.selfieMagic,
+ availableFilterLists: µb.availableFilterLists,
+ })
+ ),
+ µb.redirectEngine.toSelfie('selfie/redirectEngine'),
+ µb.staticExtFilteringEngine.toSelfie('selfie/staticExtFilteringEngine'),
+ µb.staticNetFilteringEngine.toSelfie('selfie/staticNetFilteringEngine'),
+ ]).then(( ) => {
+ µb.lz4Codec.relinquish();
});
- µb.cacheStorage.set({ selfie: selfie });
- µb.lz4Codec.relinquish();
};
- let load = function(callback) {
- µb.cacheStorage.get('selfie', function(bin) {
- if (
- bin instanceof Object === false ||
- typeof bin.selfie !== 'string'
- ) {
- return callback(false);
- }
- let selfie;
- try {
- selfie = JSON.parse(bin.selfie);
- } catch(ex) {
- }
- if (
- selfie instanceof Object === false ||
- selfie.magic !== µb.systemSettings.selfieMagic
- ) {
- return callback(false);
- }
- µb.availableFilterLists = selfie.availableFilterLists;
- µb.staticNetFilteringEngine.fromSelfie(selfie.staticNetFilteringEngine);
- µb.redirectEngine.fromSelfie(selfie.redirectEngine);
- µb.staticExtFilteringEngine.fromSelfie(selfie.staticExtFilteringEngine);
- callback(true);
+ const load = function() {
+ return Promise.all([
+ µb.assets.get('selfie/main').then(details => {
+ if (
+ details instanceof Object === false ||
+ typeof details.content !== 'string' ||
+ details.content === ''
+ ) {
+ return false;
+ }
+ let selfie;
+ try {
+ selfie = JSON.parse(details.content);
+ } catch(ex) {
+ }
+ if (
+ selfie instanceof Object === false ||
+ selfie.magic !== µb.systemSettings.selfieMagic
+ ) {
+ return false;
+ }
+ µb.availableFilterLists = selfie.availableFilterLists;
+ return true;
+ }),
+ µb.redirectEngine.fromSelfie('selfie/redirectEngine'),
+ µb.staticExtFilteringEngine.fromSelfie('selfie/staticExtFilteringEngine'),
+ µb.staticNetFilteringEngine.fromSelfie('selfie/staticNetFilteringEngine'),
+ ]).then(results =>
+ results.reduce((acc, v) => acc && v, true)
+ ).catch(reason => {
+ log.info(reason);
+ return false;
});
};
- let destroy = function() {
- if ( timer !== null ) {
+ const destroy = function() {
+ if ( timer !== undefined ) {
clearTimeout(timer);
- timer = null;
+ timer = undefined;
}
- µb.cacheStorage.remove('selfie');
- timer = vAPI.setTimeout(create, µb.selfieAfter);
+ µb.cacheStorage.remove('selfie'); // TODO: obsolete, remove eventually.
+ µb.assets.remove(/^selfie\//);
+ timer = vAPI.setTimeout(( ) => {
+ timer = undefined;
+ create();
+ }, µb.hiddenSettings.selfieAfter * 60000);
};
return {
@@ -1299,6 +1302,8 @@
// Compile the list while we have the raw version in memory
if ( topic === 'after-asset-updated' ) {
+ // Skip selfie-related content.
+ if ( details.assetKey.startsWith('selfie/') ) { return; }
var cached = typeof details.content === 'string' && details.content !== '';
if ( this.availableFilterLists.hasOwnProperty(details.assetKey) ) {
if ( cached ) {
@@ -1334,8 +1339,8 @@
cached: cached
});
// https://github.com/gorhill/uBlock/issues/2585
- // Whenever an asset is overwritten, the current selfie is quite
- // likely no longer valid.
+ // Whenever an asset is overwritten, the current selfie is quite
+ // likely no longer valid.
this.selfieManager.destroy();
return;
}
diff --git a/src/js/utils.js b/src/js/utils.js
index a9193e78bd0ad..dcee74c281db2 100644
--- a/src/js/utils.js
+++ b/src/js/utils.js
@@ -496,3 +496,112 @@
µBlock.orphanizeString = function(s) {
return JSON.parse(JSON.stringify(s));
};
+
+/******************************************************************************/
+
+// Custom base128 encoder/decoder
+//
+// TODO:
+// Could expand the LZ4 codec API to be able to return UTF8-safe string
+// representation of a compressed buffer, and thus the code below could be
+// moved LZ4 codec-side.
+
+µBlock.base128 = {
+ encode: function(arrbuf, arrlen) {
+ const inbuf = new Uint8Array(arrbuf, 0, arrlen);
+ const inputLength = arrlen;
+ let _7cnt = Math.floor(inputLength / 7);
+ let outputLength = _7cnt * 8;
+ let _7rem = inputLength % 7;
+ if ( _7rem !== 0 ) {
+ outputLength += 1 + _7rem;
+ }
+ const outbuf = new Uint8Array(outputLength);
+ let msbits, v;
+ let i = 0, j = 0;
+ while ( _7cnt-- ) {
+ v = inbuf[i+0];
+ msbits = (v & 0x80) >>> 7;
+ outbuf[j+1] = v & 0x7F;
+ v = inbuf[i+1];
+ msbits |= (v & 0x80) >>> 6;
+ outbuf[j+2] = v & 0x7F;
+ v = inbuf[i+2];
+ msbits |= (v & 0x80) >>> 5;
+ outbuf[j+3] = v & 0x7F;
+ v = inbuf[i+3];
+ msbits |= (v & 0x80) >>> 4;
+ outbuf[j+4] = v & 0x7F;
+ v = inbuf[i+4];
+ msbits |= (v & 0x80) >>> 3;
+ outbuf[j+5] = v & 0x7F;
+ v = inbuf[i+5];
+ msbits |= (v & 0x80) >>> 2;
+ outbuf[j+6] = v & 0x7F;
+ v = inbuf[i+6];
+ msbits |= (v & 0x80) >>> 1;
+ outbuf[j+7] = v & 0x7F;
+ outbuf[j+0] = msbits;
+ i += 7; j += 8;
+ }
+ if ( _7rem > 0 ) {
+ msbits = 0;
+ for ( let ir = 0; ir < _7rem; ir++ ) {
+ v = inbuf[i+ir];
+ msbits |= (v & 0x80) >>> (7 - ir);
+ outbuf[j+ir+1] = v & 0x7F;
+ }
+ outbuf[j+0] = msbits;
+ }
+ const textDecoder = new TextDecoder();
+ return textDecoder.decode(outbuf);
+ },
+ // TODO:
+ // Surprisingly, there does not seem to be any performance gain when
+ // first converting the input string into a Uint8Array through
+ // TextEncoder. Investigate again to confirm original findings and
+ // to find out whether results have changed. Not using TextEncoder()
+ // to create an intermediate input buffer lower peak memory usage
+ // at selfie load time.
+ //
+ // const textEncoder = new TextEncoder();
+ // const inbuf = textEncoder.encode(instr);
+ // const inputLength = inbuf.byteLength;
+ decode: function(instr, arrbuf) {
+ const inputLength = instr.length;
+ let _8cnt = inputLength >>> 3;
+ let outputLength = _8cnt * 7;
+ let _8rem = inputLength % 8;
+ if ( _8rem !== 0 ) {
+ outputLength += _8rem - 1;
+ }
+ const outbuf = arrbuf instanceof ArrayBuffer === false
+ ? new Uint8Array(outputLength)
+ : new Uint8Array(arrbuf);
+ let msbits;
+ let i = 0, j = 0;
+ while ( _8cnt-- ) {
+ msbits = instr.charCodeAt(i+0);
+ outbuf[j+0] = msbits << 7 & 0x80 | instr.charCodeAt(i+1);
+ outbuf[j+1] = msbits << 6 & 0x80 | instr.charCodeAt(i+2);
+ outbuf[j+2] = msbits << 5 & 0x80 | instr.charCodeAt(i+3);
+ outbuf[j+3] = msbits << 4 & 0x80 | instr.charCodeAt(i+4);
+ outbuf[j+4] = msbits << 3 & 0x80 | instr.charCodeAt(i+5);
+ outbuf[j+5] = msbits << 2 & 0x80 | instr.charCodeAt(i+6);
+ outbuf[j+6] = msbits << 1 & 0x80 | instr.charCodeAt(i+7);
+ i += 8; j += 7;
+ }
+ if ( _8rem > 1 ) {
+ msbits = instr.charCodeAt(i+0);
+ for ( let ir = 1; ir < _8rem; ir++ ) {
+ outbuf[j+ir-1] = msbits << (8-ir) & 0x80 | instr.charCodeAt(i+ir);
+ }
+ }
+ return outbuf;
+ },
+ decodeSize: function(instr) {
+ const size = (instr.length >>> 3) * 7;
+ const rem = instr.length & 7;
+ return rem === 0 ? size : size + rem - 1;
+ },
+};