Ensure disableWebAssembly setting is loaded before use

Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/899

WASM modules are now loaded on demand rather than at
script evaluation time.
This commit is contained in:
Raymond Hill 2020-02-22 13:36:22 -05:00
parent 78dd56b7a9
commit 15470bcbdc
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
5 changed files with 114 additions and 136 deletions

View file

@ -139,9 +139,7 @@ const HNTrieContainer = class {
this.buf32[TRIE1_SLOT] = this.buf32[TRIE0_SLOT];
this.buf32[CHAR0_SLOT] = details.char0 || 65536;
this.buf32[CHAR1_SLOT] = this.buf32[CHAR0_SLOT];
this.wasmInstancePromise = null;
this.wasmMemory = null;
this.readyToUse();
}
//--------------------------------------------------------------------------
@ -153,15 +151,6 @@ const HNTrieContainer = class {
this.buf32[CHAR1_SLOT] = this.buf32[CHAR0_SLOT];
}
readyToUse() {
if ( HNTrieContainer.wasmModulePromise instanceof Promise === false ) {
return Promise.resolve();
}
return HNTrieContainer.wasmModulePromise.then(
module => this.initWASM(module)
);
}
setNeedle(needle) {
if ( needle !== this.needle ) {
const buf = this.buf;
@ -407,6 +396,40 @@ const HNTrieContainer = class {
return true;
}
async enableWASM() {
if ( typeof WebAssembly !== 'object' ) { return false; }
if ( this.wasmMemory instanceof WebAssembly.Memory ) { return true; }
const module = await getWasmModule();
if ( module instanceof WebAssembly.Module === false ) {
return false;
}
const memory = new WebAssembly.Memory({ initial: 2 });
const instance = await WebAssembly.instantiate(
module,
{
imports: {
memory,
growBuf: this.growBuf.bind(this, 24, 256)
}
}
);
if ( instance instanceof WebAssembly.Instance === false ) {
return false;
}
this.wasmMemory = memory;
const curPageCount = memory.buffer.byteLength >>> 16;
const newPageCount = this.buf.byteLength + PAGE_SIZE-1 >>> 16;
if ( newPageCount > curPageCount ) {
memory.grow(newPageCount - curPageCount);
}
const buf = new Uint8Array(memory.buffer);
buf.set(this.buf);
this.buf = buf;
this.buf32 = new Uint32Array(this.buf.buffer);
this.matches = this.matchesWASM = instance.exports.matches;
this.add = this.addWASM = instance.exports.add;
}
//--------------------------------------------------------------------------
// Private methods
//--------------------------------------------------------------------------
@ -507,39 +530,6 @@ const HNTrieContainer = class {
this.buf32[CHAR1_SLOT] = char0 + charDataLen;
}
}
initWASM(module) {
if ( module instanceof WebAssembly.Module === false ) {
return Promise.resolve(null);
}
if ( this.wasmInstancePromise === null ) {
const memory = new WebAssembly.Memory({ initial: 2 });
this.wasmInstancePromise = WebAssembly.instantiate(
module,
{
imports: {
memory,
growBuf: this.growBuf.bind(this, 24, 256)
}
}
);
this.wasmInstancePromise.then(instance => {
this.wasmMemory = memory;
const curPageCount = memory.buffer.byteLength >>> 16;
const newPageCount = this.buf.byteLength + PAGE_SIZE-1 >>> 16;
if ( newPageCount > curPageCount ) {
memory.grow(newPageCount - curPageCount);
}
const buf = new Uint8Array(memory.buffer);
buf.set(this.buf);
this.buf = buf;
this.buf32 = new Uint32Array(this.buf.buffer);
this.matches = this.matchesWASM = instance.exports.matches;
this.add = this.addWASM = instance.exports.add;
});
}
return this.wasmInstancePromise;
}
};
HNTrieContainer.prototype.matches = HNTrieContainer.prototype.matchesJS;
@ -698,35 +688,8 @@ HNTrieContainer.prototype.HNTrieRef.prototype.needle = '';
// The WASM module is entirely optional, the JS implementations will be
// used should the WASM module be unavailable for whatever reason.
(( ) => {
HNTrieContainer.wasmModulePromise = null;
if (
typeof WebAssembly !== 'object' ||
typeof WebAssembly.compileStreaming !== 'function'
) {
return;
}
// Soft-dependency on vAPI so that the code here can be used outside of
// uBO (i.e. tests, benchmarks)
if ( typeof vAPI === 'object' && vAPI.canWASM !== true ) { return; }
// Soft-dependency on µBlock's advanced settings so that the code here can
// be used outside of uBO (i.e. tests, benchmarks)
if (
typeof µBlock === 'object' &&
µBlock.hiddenSettings.disableWebAssembly === true
) {
return;
}
// The wasm module will work only if CPU is natively little-endian,
// as we use native uint32 array in our js code.
const uint32s = new Uint32Array(1);
const uint8s = new Uint8Array(uint32s.buffer);
uint32s[0] = 1;
if ( uint8s[0] !== 1 ) { return; }
const getWasmModule = (( ) => {
let wasmModulePromise;
// The directory from which the current script was fetched should also
// contain the related WASM file. The script is fetched from a trusted
@ -741,15 +704,40 @@ HNTrieContainer.prototype.HNTrieRef.prototype.needle = '';
workingDir = url.href;
}
HNTrieContainer.wasmModulePromise = fetch(
workingDir + 'wasm/hntrie.wasm',
{ mode: 'same-origin' }
).then(
WebAssembly.compileStreaming
).catch(reason => {
HNTrieContainer.wasmModulePromise = null;
log.info(reason);
});
return async function() {
if ( wasmModulePromise instanceof Promise ) {
return wasmModulePromise;
}
if (
typeof WebAssembly !== 'object' ||
typeof WebAssembly.compileStreaming !== 'function'
) {
return;
}
// Soft-dependency on vAPI so that the code here can be used outside of
// uBO (i.e. tests, benchmarks)
if ( typeof vAPI === 'object' && vAPI.canWASM !== true ) { return; }
// The wasm module will work only if CPU is natively little-endian,
// as we use native uint32 array in our js code.
const uint32s = new Uint32Array(1);
const uint8s = new Uint8Array(uint32s.buffer);
uint32s[0] = 1;
if ( uint8s[0] !== 1 ) { return; }
wasmModulePromise = fetch(
workingDir + 'wasm/hntrie.wasm',
{ mode: 'same-origin' }
).then(
WebAssembly.compileStreaming
).catch(reason => {
log.info(reason);
});
return wasmModulePromise;
};
})();
/******************************************************************************/

View file

@ -263,6 +263,12 @@ try {
await µb.loadHiddenSettings();
log.info(`Hidden settings ready ${Date.now()-vAPI.T0} ms after launch`);
if ( µb.hiddenSettings.disableWebAssembly !== true ) {
µb.staticNetFilteringEngine.enableWASM().then(( ) => {
log.info(`Static filtering engine WASM modules ready ${Date.now()-vAPI.T0} ms after launch`);
});
}
const cacheBackend = await µb.cacheStorage.select(
µb.hiddenSettings.cacheStorageAPI
);

View file

@ -311,26 +311,15 @@ const bidiTrieMatchExtra = function(l, r, ix) {
return 0;
};
const bidiTrie = (( ) => {
let trieDetails;
try {
trieDetails = JSON.parse(
vAPI.localStorage.getItem('SNFE.bidiTrieDetails')
);
} catch(ex) {
}
const trie = new µb.BidiTrieContainer(trieDetails, bidiTrieMatchExtra);
if ( µb.hiddenSettings.disableWebAssembly !== true ) {
trie.enableWASM();
}
return trie;
})();
const bidiTrie = new µb.BidiTrieContainer(
vAPI.localStorage.getItem('SNFE.bidiTrieDetails'),
bidiTrieMatchExtra
);
const bidiTrieOptimize = function(shrink = false) {
const trieDetails = bidiTrie.optimize(shrink);
vAPI.localStorage.setItem(
'SNFE.bidiTrieDetails',
JSON.stringify(trieDetails)
bidiTrie.optimize(shrink)
);
};
@ -1157,14 +1146,9 @@ registerFilterClass(FilterRegex);
const filterOrigin = new (class {
constructor() {
let trieDetails;
try {
trieDetails = JSON.parse(
vAPI.localStorage.getItem('FilterOrigin.trieDetails')
);
} catch(ex) {
}
this.trieContainer = new µb.HNTrieContainer(trieDetails);
this.trieContainer = new µb.HNTrieContainer(
vAPI.localStorage.getItem('FilterOrigin.trieDetails')
);
this.strToUnitMap = new Map();
this.gcTimer = undefined;
}
@ -1244,10 +1228,9 @@ const filterOrigin = new (class {
}
optimize() {
const trieDetails = this.trieContainer.optimize();
vAPI.localStorage.setItem(
'FilterOrigin.trieDetails',
JSON.stringify(trieDetails)
this.trieContainer.optimize()
);
}
@ -1689,10 +1672,9 @@ const FilterHostnameDict = class {
}
static optimize() {
const trieDetails = FilterHostnameDict.trieContainer.optimize();
vAPI.localStorage.setItem(
'FilterHostnameDict.trieDetails',
JSON.stringify(trieDetails)
FilterHostnameDict.trieContainer.optimize()
);
}
@ -1701,16 +1683,9 @@ const FilterHostnameDict = class {
}
};
FilterHostnameDict.trieContainer = (( ) => {
let trieDetails;
try {
trieDetails = JSON.parse(
vAPI.localStorage.getItem('FilterHostnameDict.trieDetails')
);
} catch(ex) {
}
return new µb.HNTrieContainer(trieDetails);
})();
FilterHostnameDict.trieContainer = new µb.HNTrieContainer(
vAPI.localStorage.getItem('FilterHostnameDict.trieDetails')
);
registerFilterClass(FilterHostnameDict);
@ -3445,6 +3420,16 @@ FilterContainer.prototype.getFilterCount = function() {
/******************************************************************************/
FilterContainer.prototype.enableWASM = function() {
return Promise.all([
bidiTrie.enableWASM(),
filterOrigin.trieContainer.enableWASM(),
FilterHostnameDict.trieContainer.enableWASM(),
]);
};
/******************************************************************************/
// action: 1=test, 2=record
FilterContainer.prototype.benchmark = async function(action, target) {

View file

@ -198,7 +198,6 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
µBlock.saveImmediateHiddenSettings = function() {
const props = [
'consoleLogLevel',
'disableWebAssembly',
'suspendTabsUntilReady',
];
const toSave = {};
@ -1011,7 +1010,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
/******************************************************************************/
µBlock.loadPublicSuffixList = async function() {
if ( this.hiddenSettings.disableWebAssembly === false ) {
if ( this.hiddenSettings.disableWebAssembly !== true ) {
publicSuffixList.enableWASM();
}

View file

@ -914,7 +914,20 @@ const roundToPageSize = v => (v + PAGE_SIZE-1) & ~(PAGE_SIZE-1);
const getWasmModule = (( ) => {
let wasmModulePromise;
return function() {
// The directory from which the current script was fetched should also
// contain the related WASM file. The script is fetched from a trusted
// location, and consequently so will be the related WASM file.
let workingDir;
{
const url = new URL(document.currentScript.src);
const match = /[^\/]+$/.exec(url.pathname);
if ( match !== null ) {
url.pathname = url.pathname.slice(0, match.index);
}
workingDir = url.href;
}
return async function() {
if ( wasmModulePromise instanceof Promise ) {
return wasmModulePromise;
}
@ -937,19 +950,6 @@ const getWasmModule = (( ) => {
uint32s[0] = 1;
if ( uint8s[0] !== 1 ) { return; }
// The directory from which the current script was fetched should also
// contain the related WASM file. The script is fetched from a trusted
// location, and consequently so will be the related WASM file.
let workingDir;
{
const url = new URL(document.currentScript.src);
const match = /[^\/]+$/.exec(url.pathname);
if ( match !== null ) {
url.pathname = url.pathname.slice(0, match.index);
}
workingDir = url.href;
}
wasmModulePromise = fetch(
workingDir + 'wasm/biditrie.wasm',
{ mode: 'same-origin' }