From 15470bcbdc8d54dcd68ae7face4fa2a6e2e24bd0 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 22 Feb 2020 13:36:22 -0500 Subject: [PATCH] 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. --- src/js/hntrie.js | 152 +++++++++++++++------------------ src/js/start.js | 6 ++ src/js/static-net-filtering.js | 61 +++++-------- src/js/storage.js | 3 +- src/js/strie.js | 28 +++--- 5 files changed, 114 insertions(+), 136 deletions(-) diff --git a/src/js/hntrie.js b/src/js/hntrie.js index 22f917300..ba5d280cb 100644 --- a/src/js/hntrie.js +++ b/src/js/hntrie.js @@ -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; + }; })(); /******************************************************************************/ diff --git a/src/js/start.js b/src/js/start.js index d623bfe06..4aa61de28 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -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 ); diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 0fd0908aa..c0b3ae346 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -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) { diff --git a/src/js/storage.js b/src/js/storage.js index a3994b5c6..f38958c2a 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -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(); } diff --git a/src/js/strie.js b/src/js/strie.js index 99b8a9adc..fa109854c 100644 --- a/src/js/strie.js +++ b/src/js/strie.js @@ -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' }