diff --git a/ext/wasm/GNUmakefile b/ext/wasm/GNUmakefile index f461f8b46a..f364e446bc 100644 --- a/ext/wasm/GNUmakefile +++ b/ext/wasm/GNUmakefile @@ -90,7 +90,7 @@ dir.tool := $(dir.top)/tool # the loading-from-worker case... # # dir.dout = output dir for deliverables. -dir.dout := $(dir.wasm) +dir.dout := $(dir.wasm)/jswasm # dir.tmp = output dir for intermediary build files, as opposed to # end-user deliverables. dir.tmp := $(dir.wasm)/bld @@ -218,6 +218,20 @@ sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js sqlite3-api.jses += $(dir.api)/sqlite3-api-opfs.js sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js +# "External" API files which are part of our distribution +# but not part of the sqlite3-api.js amalgamation. +SOAP.js := $(dir.api)/sqlite3-opfs-async-proxy.js +sqlite3-worker1.js := $(dir.api)/sqlite3-worker1.js +sqlite3-worker1-promiser.js := $(dir.api)/sqlite3-worker1-promiser.js +define CP_XAPI +sqlite3-api.ext.jses += $$(dir.dout)/$$(notdir $(1)) +$$(dir.dout)/$$(notdir $(1)): $(1) $$(MAKEFILE) + cp $$< $$@ +endef +$(foreach X,$(SOAP.js) $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js),\ + $(eval $(call CP_XAPI,$(X)))) +all: $(sqlite3-api.ext.jses) + sqlite3-api.js := $(dir.tmp)/sqlite3-api.js $(sqlite3-api.js): $(sqlite3-api.jses) $(MAKEFILE) @echo "Making $@..." @@ -384,6 +398,7 @@ sqlite3-wasm.c := $(dir.api)/sqlite3-wasm.c # difference. Thus we build all binaries against sqlite3-wasm.c # instead of building a shared copy of sqlite3-wasm.o. $(eval $(call call-make-pre-js,sqlite3)) +$(sqlite3.js): $(sqlite3.js): $(MAKEFILE) $(sqlite3.wasm.obj) \ $(EXPORTED_FUNCTIONS.api) \ $(pre-post-sqlite3.deps) diff --git a/ext/wasm/api/extern-post-js.js b/ext/wasm/api/extern-post-js.js index 7dba03b3a7..5f5f72d4b3 100644 --- a/ext/wasm/api/extern-post-js.js +++ b/ext/wasm/api/extern-post-js.js @@ -16,6 +16,27 @@ if(!originalInit){ throw new Error("Expecting self.sqlite3InitModule to be defined by the Emscripten build."); } + /** + We need to add some state which our custom Module.locateFile() + can see, but an Emscripten limitation currently prevents us from + attaching it to the sqlite3InitModule function object: + + https://github.com/emscripten-core/emscripten/issues/18071 + + The only current workaround is to temporarily stash this state + into the global scope and delete it when sqlite3InitModule() + is called. + */ + const initModuleState = self.sqlite3InitModuleState = Object.assign(Object.create(null),{ + moduleScript: self?.document?.currentScript, + isWorker: (!self.document && self.window !== self), + location: self.location, + urlParams: new URL(self.location.href).searchParams + }); + if(initModuleState.urlParams.has('sqlite3.dir')){ + initModuleState.sqlite3Dir = initModuleState.urlParams.get('sqlite3.dir') +'/'; + }; + self.sqlite3InitModule = (...args)=>{ //console.warn("Using replaced sqlite3InitModule()",self.location); return originalInit(...args).then((EmscriptenModule)=>{ @@ -32,6 +53,8 @@ Emscripten details. */ return EmscriptenModule; } + EmscriptenModule.sqlite3.scriptInfo = initModuleState; + //console.warn("sqlite3.scriptInfo =",EmscriptenModule.sqlite3.scriptInfo); const f = EmscriptenModule.sqlite3.asyncPostInit; delete EmscriptenModule.sqlite3.asyncPostInit; return f(); @@ -41,13 +64,19 @@ }); }; self.sqlite3InitModule.ready = originalInit.ready; - //console.warn("Replaced sqlite3InitModule()"); -})(); -if(0){ - console.warn("self.location.href =",self.location.href); - if('undefined' !== typeof document){ - console.warn("document.currentScript.src =", - document?.currentScript?.src); + if(self.sqlite3InitModuleState.moduleScript){ + const sim = self.sqlite3InitModuleState; + let src = sim.moduleScript.src.split('/'); + src.pop(); + sim.scriptDir = src.join('/') + '/'; } -} + if(0){ + console.warn("Replaced sqlite3InitModule()"); + console.warn("self.location.href =",self.location.href); + if('undefined' !== typeof document){ + console.warn("document.currentScript.src =", + document?.currentScript?.src); + } + } +})(); diff --git a/ext/wasm/api/pre-js.js b/ext/wasm/api/pre-js.js index b6630416dd..c07d0373cd 100644 --- a/ext/wasm/api/pre-js.js +++ b/ext/wasm/api/pre-js.js @@ -4,9 +4,49 @@ This file is intended to be prepended to the sqlite3.js build using Emscripten's --pre-js=THIS_FILE flag (or equivalent). */ + +// See notes in extern-post-js.js +const sqlite3InitModuleState = self.sqlite3InitModuleState || Object.create(null); +delete self.sqlite3InitModuleState; + +/** + This custom locateFile() tries to figure out where to load `path` + from. The intent is to provide a way for foo/bar/X.js loaded from a + Worker constructor or importScripts() to be able to resolve + foo/bar/X.wasm (in the latter case, with some help): + + 1) If URL param named the same as `path` is set, it is returned. + + 2) If sqlite3InitModuleState.sqlite3Dir is set, then (thatName + path) + is returned (note that it's assumed to end with '/'). + + 3) If this code is running in the main UI thread AND it was loaded + from a SCRIPT tag, the directory part of that URL is used + as the prefix. (This form of resolution unfortunately does not + function for scripts loaded via importScripts().) + + 4) If none of the above apply, (prefix+path) is returned. +*/ Module['locateFile'] = function(path, prefix) { - return prefix + path; -}; + let theFile; + const up = this.urlParams; + if(0){ + console.warn("locateFile(",arguments[0], ',', arguments[1],")", + 'self.location =',self.location, + 'sqlite3InitModuleState.scriptDir =',this.scriptDir, + 'up.entries() =',Array.from(up.entries())); + } + if(up.has(path)){ + theFile = up.get(path); + }else if(this.sqlite3Dir){ + theFile = this.sqlite3Dir + path; + }else if(this.scriptDir){ + theFile = this.scriptDir + path; + }else{ + theFile = prefix + path; + } + return theFile; +}.bind(sqlite3InitModuleState); /** Bug warning: this xInstantiateWasm bit must remain disabled diff --git a/ext/wasm/api/sqlite3-api-opfs.js b/ext/wasm/api/sqlite3-api-opfs.js index 98defe4fbc..9f53024548 100644 --- a/ext/wasm/api/sqlite3-api-opfs.js +++ b/ext/wasm/api/sqlite3-api-opfs.js @@ -98,6 +98,9 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){ options.proxyUri = callee.defaultProxyUri; } + if('function' === typeof options.proxyUri){ + options.proxyUri = options.proxyUri(); + } const thePromise = new Promise(function(promiseResolve, promiseReject_){ const loggers = { 0:console.error.bind(console), @@ -1092,9 +1095,18 @@ installOpfsVfs.defaultProxyUri = "sqlite3-opfs-async-proxy.js"; //console.warn("sqlite3.installOpfsVfs.defaultProxyUri =",sqlite3.installOpfsVfs.defaultProxyUri); self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{ + if(sqlite3.scriptInfo && !sqlite3.scriptInfo.isWorker){ + return; + } try{ + let proxyJs = installOpfsVfs.defaultProxyUri; + if(sqlite3.scriptInfo.sqlite3Dir){ + installOpfsVfs.defaultProxyUri = + sqlite3.scriptInfo.sqlite3Dir + proxyJs; + //console.warn("installOpfsVfs.defaultProxyUri =",installOpfsVfs.defaultProxyUri); + } return installOpfsVfs().catch((e)=>{ - console.warn("Ignoring inability to install OPFS sqlite3_vfs:",e); + console.warn("Ignoring inability to install OPFS sqlite3_vfs:",e.message); }); }catch(e){ console.error("installOpfsVfs() exception:",e); diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index f8c0f024e6..3980e0ad42 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -1334,7 +1334,20 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( //while(lip.length) p = p.then(lip.shift()); //return p.then(()=>sqlite3); return Promise.all(lip).then(()=>sqlite3); - } + }, + /** + scriptInfo ideally gets injected into this object by the + infrastructure which assembles the JS/WASM module. It contains + state which must be collected before sqlite3ApiBootstrap() can + be declared. It is not necessarily available to any + sqlite3ApiBootstrap.initializers but "should" be in place (if + it's added at all) by the time that + sqlite3ApiBootstrap.initializersAsync is processed. + + This state is not part of the public API, only intended for use + with the sqlite3 API bootstrapping and wasm-loading process. + */ + scriptInfo: undefined }; try{ sqlite3ApiBootstrap.initializers.forEach((f)=>{ @@ -1410,3 +1423,4 @@ self.sqlite3ApiBootstrap.defaultConfig = Object.create(null); value which will be stored here. */ self.sqlite3ApiBootstrap.sqlite3 = undefined; + diff --git a/ext/wasm/sqlite3-opfs-async-proxy.js b/ext/wasm/api/sqlite3-opfs-async-proxy.js similarity index 100% rename from ext/wasm/sqlite3-opfs-async-proxy.js rename to ext/wasm/api/sqlite3-opfs-async-proxy.js diff --git a/ext/wasm/sqlite3-worker1-promiser.js b/ext/wasm/api/sqlite3-worker1-promiser.js similarity index 93% rename from ext/wasm/sqlite3-worker1-promiser.js rename to ext/wasm/api/sqlite3-worker1-promiser.js index 71e2e33135..a77b0126df 100644 --- a/ext/wasm/sqlite3-worker1-promiser.js +++ b/ext/wasm/api/sqlite3-worker1-promiser.js @@ -237,6 +237,23 @@ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){ }; }/*sqlite3Worker1Promiser()*/; self.sqlite3Worker1Promiser.defaultConfig = { - worker: ()=>new Worker("sqlite3-worker1.js"+self.location.search), + worker: function(){ + let theJs = "sqlite3-worker1.js"; + if(this.currentScript){ + const src = this.currentScript.src.split('/'); + src.pop(); + theJs = src.join('/')+'/' + theJs; + //console.warn("promiser currentScript, theJs =",this.currentScript,theJs); + }else{ + //console.warn("promiser self.location =",self.location); + const urlParams = new URL(self.location.href).searchParams; + if(urlParams.has('sqlite3.dir')){ + theJs = urlParams.get('sqlite3.dir') + '/' + theJs; + } + } + return new Worker(theJs + self.location.search); + }.bind({ + currentScript: self?.document?.currentScript + }), onerror: (...args)=>console.error('worker1 promiser error',...args) }; diff --git a/ext/wasm/sqlite3-worker1.js b/ext/wasm/api/sqlite3-worker1.js similarity index 94% rename from ext/wasm/sqlite3-worker1.js rename to ext/wasm/api/sqlite3-worker1.js index fef155e1f8..bc860300bb 100644 --- a/ext/wasm/sqlite3-worker1.js +++ b/ext/wasm/api/sqlite3-worker1.js @@ -29,23 +29,21 @@ This file accepts a couple of URL arguments to adjust how it loads sqlite3.js: - - `sqlite3.dir`, if set, treats the given directory name as the - directory from which `sqlite3.js` will be loaded. - `sqlite3.js`, if set, is used as the URI to `sqlite3.js` and it may contain path elements, e.g. `sqlite3.js=foo/bar/my-sqlite3.js`. + - `sqlite3.dir`, if set, treats the given directory name as the + directory from which `sqlite3.js` will be loaded. By default is loads 'sqlite3.js'. */ "use strict"; (()=>{ const urlParams = new URL(self.location.href).searchParams; - let theJs; + let theJs = 'sqlite3.js'; if(urlParams.has('sqlite3.js')){ theJs = urlParams.get('sqlite3.js'); }else if(urlParams.has('sqlite3.dir')){ - theJs = urlParams.get('sqlite3.dir')+'/sqlite3.js'; - }else{ - theJs = 'sqlite3.js'; + theJs = urlParams.get('sqlite3.dir') + '/' + theJs; } importScripts(theJs); sqlite3InitModule().then((sqlite3)=>{ diff --git a/ext/wasm/batch-runner.html b/ext/wasm/batch-runner.html index e7a2b1e082..5258f9597e 100644 --- a/ext/wasm/batch-runner.html +++ b/ext/wasm/batch-runner.html @@ -69,7 +69,7 @@
- + - +