From 79669c25c48fee3f2e9f3cb9e43f1ee3b2f023df Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 25 May 2022 03:08:22 +0000 Subject: [PATCH] fiddle: refactored so that it no longer exposes any global symbols. Doing so with the main sqlite3.api module will be much tricker. FossilOrigin-Name: cd227be805d0cd4b6e3c72ed0992ad3aec3db9c366909d9d82c6d3a29009c6eb --- Makefile.in | 3 + ext/fiddle/EXPORTED_RUNTIME_METHODS | 1 + ext/fiddle/fiddle-worker.js | 405 ++++++++++++++-------------- ext/fiddle/fiddle.html | 9 +- ext/fiddle/fiddle.js | 5 +- manifest | 20 +- manifest.uuid | 2 +- 7 files changed, 235 insertions(+), 210 deletions(-) diff --git a/Makefile.in b/Makefile.in index 8a6d04a1de..b49c987bfe 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1539,6 +1539,9 @@ emcc_flags = $(emcc_opt) -sALLOW_TABLE_GROWTH -I. $(SHELL_OPT) $(fiddle_module_js): Makefile sqlite3.c shell.c \ $(fiddle_dir)/EXPORTED_RUNTIME_METHODS $(fiddle_dir)/EXPORTED_FUNCTIONS.fiddle emcc -o $@ $(emcc_flags) \ + -sMODULARIZE \ + -sEXPORT_NAME=initFiddleModule \ + -sENVIRONMENT=web \ -sEXPORTED_RUNTIME_METHODS=@$(fiddle_dir_abs)/EXPORTED_RUNTIME_METHODS \ -sEXPORTED_FUNCTIONS=@$(fiddle_dir_abs)/EXPORTED_FUNCTIONS.fiddle \ sqlite3.c shell.c diff --git a/ext/fiddle/EXPORTED_RUNTIME_METHODS b/ext/fiddle/EXPORTED_RUNTIME_METHODS index 1bfcc97d40..fed87ecd78 100644 --- a/ext/fiddle/EXPORTED_RUNTIME_METHODS +++ b/ext/fiddle/EXPORTED_RUNTIME_METHODS @@ -9,3 +9,4 @@ addFunction setValue getValue allocate +FS diff --git a/ext/fiddle/fiddle-worker.js b/ext/fiddle/fiddle-worker.js index 8d331bff15..3707e85a84 100644 --- a/ext/fiddle/fiddle-worker.js +++ b/ext/fiddle/fiddle-worker.js @@ -88,211 +88,226 @@ annoying. */ "use strict"; - -/** - Posts a message in the form {type,data} unless passed more than 2 - args, in which case it posts {type, data:[arg1...argN]}. -*/ -const wMsg = function(type,data){ - postMessage({ - type, - data: arguments.length<3 - ? data - : Array.prototype.slice.call(arguments,1) - }); -}; - -self.onerror = function(/*message, source, lineno, colno, error*/) { - const err = arguments[4]; - if(err && 'ExitStatus'==err.name){ - /* This is relevant for the sqlite3 shell binding but not the - lower-level binding. */ - Module._isDead = true; - Module.printErr("FATAL ERROR:", err.message); - Module.printErr("Restarting the app requires reloading the page."); - wMsg('error', err); - } - Module.setStatus('Exception thrown, see JavaScript console'); - Module.setStatus = function(text) { - console.error('[post-exception status]', text); - }; -}; - -self.Module = { -/* ^^^ cannot declare that const because fiddle-module.js - (auto-generated) includes a decl for it and runs in this scope. */ - preRun: [], - postRun: [], - //onRuntimeInitialized: function(){}, - print: function(text){wMsg('stdout', Array.prototype.slice.call(arguments));}, - printErr: function(text){wMsg('stderr', Array.prototype.slice.call(arguments));}, +(function(){ /** - Intercepts status updates from the Module object and fires - worker events with a type of 'status' and a payload of: - - { - text: string | null, // null at end of load process - step: integer // starts at 1, increments 1 per call - } - - We have no way of knowing in advance how many steps will - be processed/posted, so creating a "percentage done" view is - not really practical. One can be approximated by giving it a - current value of message.step and max value of message.step+1, - though. - - When work is finished, a message with a text value of null is - submitted. - - After a message with text==null is posted, the module may later - post messages about fatal problems, e.g. an exit() being - triggered, so it is recommended that UI elements for posting - status messages not be outright removed from the DOM when - text==null, and that they instead be hidden until/unless - text!=null. + Posts a message in the form {type,data} unless passed more than 2 + args, in which case it posts {type, data:[arg1...argN]}. */ - setStatus: function f(text){ - if(!f.last) f.last = { step: 0, text: '' }; - else if(text === f.last.text) return; - f.last.text = text; - wMsg('module',{ - type:'status', - data:{step: ++f.last.step, text: text||null} + const wMsg = function(type,data){ + postMessage({ + type, + data: arguments.length<3 + ? data + : Array.prototype.slice.call(arguments,1) }); - }, - totalDependencies: 0, - monitorRunDependencies: function(left) { - this.totalDependencies = Math.max(this.totalDependencies, left); - this.setStatus(left - ? ('Preparing... (' + (this.totalDependencies-left) - + '/' + this.totalDependencies + ')') - : 'All downloads complete.'); - } -}; - -const Sqlite3Shell = { - /** Returns the name of the currently-opened db. */ - dbFilename: function f(){ - if(!f._) f._ = Module.cwrap('fiddle_db_filename', "string", ['string']); - return f._(); - }, - /** - Runs the given text through the shell as if it had been typed - in by a user. Fires a working/start event before it starts and - working/end event when it finishes. - */ - exec: function f(sql){ - if(!f._) f._ = Module.cwrap('fiddle_exec', null, ['string']); - if(Module._isDead){ - wMsg('stderr', "shell module has exit()ed. Cannot run SQL."); - return; - } - wMsg('working','start'); - try { - if(f._running){ - wMsg('stderr','Cannot run multiple commands concurrently.'); - }else{ - f._running = true; - f._(sql); - } - } finally { - delete f._running; - wMsg('working','end'); - } - }, - /* Interrupt can't work: this Worker is tied up working, so won't get the - interrupt event which would be needed to perform the interrupt. */ - interrupt: function f(){ - if(!f._) f._ = Module.cwrap('fiddle_interrupt', null); - wMsg('stdout',"Requesting interrupt."); - f._(); - } -}; + }; -self.onmessage = function f(ev){ - ev = ev.data; - if(!f.cache){ - f.cache = { - prevFilename: null + self.onerror = function(/*message, source, lineno, colno, error*/) { + const err = arguments[4]; + if(err && 'ExitStatus'==err.name){ + /* This is relevant for the sqlite3 shell binding but not the + lower-level binding. */ + fiddleModule._isDead = true; + fiddleModule.printErr("FATAL ERROR:", err.message); + fiddleModule.printErr("Restarting the app requires reloading the page."); + wMsg('error', err); + } + fiddleModule.setStatus('Exception thrown, see JavaScript console'); + fiddleModule.setStatus = function(text) { + console.error('[post-exception status]', text); }; - } - //console.debug("worker: onmessage.data",ev); - switch(ev.type){ - case 'shellExec': Sqlite3Shell.exec(ev.data); return; - case 'interrupt': Sqlite3Shell.interrupt(); return; - /** Triggers the export of the current db. Fires an - event in the form: - - {type:'db-export', - data:{ - filename: name of db, - buffer: contents of the db file (Uint8Array), - error: on error, a message string and no buffer property. - } - } + }; + + const Sqlite3Shell = { + /** Returns the name of the currently-opened db. */ + dbFilename: function f(){ + if(!f._) f._ = fiddleModule.cwrap('fiddle_db_filename', "string", ['string']); + return f._(); + }, + /** + Runs the given text through the shell as if it had been typed + in by a user. Fires a working/start event before it starts and + working/end event when it finishes. */ - case 'db-export': { - const fn = Sqlite3Shell.dbFilename(); - wMsg('stdout',"Exporting",fn+"."); - const fn2 = fn ? fn.split(/[/\\]/).pop() : null; - try{ - if(!fn2) throw new Error("DB appears to be closed."); - wMsg('db-export',{ - filename: fn2, - buffer: FS.readFile(fn, {encoding:"binary"}) - }); - }catch(e){ - /* Post a failure message so that UI elements disabled - during the export can be re-enabled. */ - wMsg('db-export',{ - filename: fn, - error: e.message - }); + exec: function f(sql){ + if(!f._) f._ = fiddleModule.cwrap('fiddle_exec', null, ['string']); + if(fiddleModule._isDead){ + wMsg('stderr', "shell module has exit()ed. Cannot run SQL."); + return; + } + wMsg('working','start'); + try { + if(f._running){ + wMsg('stderr','Cannot run multiple commands concurrently.'); + }else{ + f._running = true; + f._(sql); + } + } finally { + delete f._running; + wMsg('working','end'); } - return; + }, + /* Interrupt can't work: this Worker is tied up working, so won't get the + interrupt event which would be needed to perform the interrupt. */ + interrupt: function f(){ + if(!f._) f._ = fiddleModule.cwrap('fiddle_interrupt', null); + wMsg('stdout',"Requesting interrupt."); + f._(); + } + }; + + self.onmessage = function f(ev){ + ev = ev.data; + if(!f.cache){ + f.cache = { + prevFilename: null + }; } - case 'open': { - /* Expects: { - buffer: ArrayBuffer | Uint8Array, - filename: for logging/informational purposes only - } */ - const opt = ev.data; - let buffer = opt.buffer; - if(buffer instanceof Uint8Array){ - }else if(buffer instanceof ArrayBuffer){ - buffer = new Uint8Array(buffer); - }else{ - wMsg('stderr',"'open' expects {buffer:Uint8Array} containing an uploaded db."); + //console.debug("worker: onmessage.data",ev); + switch(ev.type){ + case 'shellExec': Sqlite3Shell.exec(ev.data); return; + case 'interrupt': Sqlite3Shell.interrupt(); return; + /** Triggers the export of the current db. Fires an + event in the form: + + {type:'db-export', + data:{ + filename: name of db, + buffer: contents of the db file (Uint8Array), + error: on error, a message string and no buffer property. + } + } + */ + case 'db-export': { + const fn = Sqlite3Shell.dbFilename(); + wMsg('stdout',"Exporting",fn+"."); + const fn2 = fn ? fn.split(/[/\\]/).pop() : null; + try{ + if(!fn2) throw new Error("DB appears to be closed."); + wMsg('db-export',{ + filename: fn2, + buffer: fiddleModule.FS.readFile(fn, {encoding:"binary"}) + }); + }catch(e){ + /* Post a failure message so that UI elements disabled + during the export can be re-enabled. */ + wMsg('db-export',{ + filename: fn, + error: e.message + }); + } return; } - const fn = ( - opt.filename - ? opt.filename.split(/[/\\]/).pop().replace('"','_') - : ("db-"+((Math.random() * 10000000) | 0)+ - "-"+((Math.random() * 10000000) | 0)+".sqlite3") - ); - /* We cannot delete the existing db file until the new one - is installed, which means that we risk overflowing our - quota (if any) by having both the previous and current - db briefly installed in the virtual filesystem. */ - FS.createDataFile("/", fn, buffer, true, true); - const oldName = Sqlite3Shell.dbFilename(); - Sqlite3Shell.exec('.open "/'+fn+'"'); - if(oldName !== fn){ - FS.unlink(oldName); + case 'open': { + /* Expects: { + buffer: ArrayBuffer | Uint8Array, + filename: for logging/informational purposes only + } */ + const opt = ev.data; + let buffer = opt.buffer; + if(buffer instanceof Uint8Array){ + }else if(buffer instanceof ArrayBuffer){ + buffer = new Uint8Array(buffer); + }else{ + wMsg('stderr',"'open' expects {buffer:Uint8Array} containing an uploaded db."); + return; + } + const fn = ( + opt.filename + ? opt.filename.split(/[/\\]/).pop().replace('"','_') + : ("db-"+((Math.random() * 10000000) | 0)+ + "-"+((Math.random() * 10000000) | 0)+".sqlite3") + ); + /* We cannot delete the existing db file until the new one + is installed, which means that we risk overflowing our + quota (if any) by having both the previous and current + db briefly installed in the virtual filesystem. */ + fiddleModule.FS.createDataFile("/", fn, buffer, true, true); + const oldName = Sqlite3Shell.dbFilename(); + Sqlite3Shell.exec('.open "/'+fn+'"'); + if(oldName !== fn){ + fiddleModule.FS.unlink(oldName); + } + wMsg('stdout',"Replaced DB with",fn+"."); + return; } - wMsg('stdout',"Replaced DB with",fn+"."); - return; + }; + console.warn("Unknown fiddle-worker message type:",ev); + }; + + /** + emscripten module for use with build mode -sMODULARIZE. + */ + const fiddleModule = { + /* ^^^ cannot declare that const because fiddle-module.js + (auto-generated) includes a decl for it and runs in this scope. */ + preRun: [], + postRun: [ + /*function(M) { + console.debug("FS=",M.FS); + wMsg('fiddle-ready'); + }*/ + ], + print: function(text){wMsg('stdout', Array.prototype.slice.call(arguments));}, + printErr: function(text){wMsg('stderr', Array.prototype.slice.call(arguments));}, + onRuntimeInitialized: function(M) { + //console.debug("M=",M); + wMsg('fiddle-ready'); + }, + /** + Intercepts status updates from the Module object and fires + worker events with a type of 'status' and a payload of: + + { + text: string | null, // null at end of load process + step: integer // starts at 1, increments 1 per call + } + + We have no way of knowing in advance how many steps will + be processed/posted, so creating a "percentage done" view is + not really practical. One can be approximated by giving it a + current value of message.step and max value of message.step+1, + though. + + When work is finished, a message with a text value of null is + submitted. + + After a message with text==null is posted, the module may later + post messages about fatal problems, e.g. an exit() being + triggered, so it is recommended that UI elements for posting + status messages not be outright removed from the DOM when + text==null, and that they instead be hidden until/unless + text!=null. + */ + setStatus: function f(text){ + if(!f.last) f.last = { step: 0, text: '' }; + else if(text === f.last.text) return; + f.last.text = text; + wMsg('module',{ + type:'status', + data:{step: ++f.last.step, text: text||null} + }); + }, + totalDependencies: 0, + monitorRunDependencies: function(left) { + this.totalDependencies = Math.max(this.totalDependencies, left); + this.setStatus(left + ? ('Preparing... (' + (this.totalDependencies-left) + + '/' + this.totalDependencies + ')') + : 'All downloads complete.'); } }; - console.warn("Unknown fiddle-worker message type:",ev); -}; -self.Module.setStatus('Downloading...'); -importScripts('fiddle-module.js') -/* loads the wasm module and notifies, via Module.setStatus() and - Module.onRuntimeInitialized(), when it's done loading. The latter - is called _before_ the final call to Module.setStatus(). */; - -Module["onRuntimeInitialized"] = function onRuntimeInitialized() { - wMsg('fiddle-ready'); -}; + + + importScripts('fiddle-module.js') + /* loads the wasm module and installs our module init function, + initFiddleModule(). */; + /** + initFiddleModule() is installed via fiddle-module.js due to + building with: + + emcc ... -sMODULARIZE=1 -sEXPORT_NAME=initFiddleModule + */ + initFiddleModule(fiddleModule); +})(); diff --git a/ext/fiddle/fiddle.html b/ext/fiddle/fiddle.html index 3f00e8eb7c..d8625cbf64 100644 --- a/ext/fiddle/fiddle.html +++ b/ext/fiddle/fiddle.html @@ -4,6 +4,9 @@ sqlite3 fiddle + + @@ -71,7 +74,7 @@ display: none !important; } fieldset.options { - font-size: 75%; + font-size: 70%; } fieldset > legend { padding: 0 0.5em; @@ -172,6 +175,9 @@ + + +
@@ -188,7 +194,6 @@ SELECT * FROM t; -
diff --git a/ext/fiddle/fiddle.js b/ext/fiddle/fiddle.js index 54d2be99c3..95f57c15a7 100644 --- a/ext/fiddle/fiddle.js +++ b/ext/fiddle/fiddle.js @@ -79,7 +79,7 @@ f._.value = ''; } if(this.config.echoToConsole) console.log(text); - if(this.jqTerm) window.Module.jqTerm.echo(text); + if(this.jqTerm) this.jqTerm.echo(text); f._.value += text + "\n"; if(this.config.autoScrollOutput){ f._.scrollTop = f._.scrollHeight; @@ -527,7 +527,7 @@ SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;`} if(window.jQuery && window.jQuery.terminal){ /* Set up the terminal-style view... */ const eTerm = window.jQuery('#view-terminal').empty(); - SF.jqTerm = eTerm.terminal(dbExec,{ + SF.jqTerm = eTerm.terminal(SF.dbExec.bind(SF),{ prompt: 'sqlite> ', greetings: false /* note that the docs incorrectly call this 'greeting' */ }); @@ -535,6 +535,7 @@ SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;`} const head = E('header#titlebar'); const btnToggleView = document.createElement('button'); btnToggleView.appendChild(document.createTextNode("Toggle View")); + head.appendChild(btnToggleView); btnToggleView.addEventListener('click',function f(){ EAll('.app-view').forEach(e=>e.classList.toggle('hidden')); if(document.body.classList.toggle('terminal-mode')){ diff --git a/manifest b/manifest index 803bf329b9..1b66446908 100644 --- a/manifest +++ b/manifest @@ -1,9 +1,9 @@ -C fiddle:\sadded\ssupport\sfor\sexporting\s(downloading)\sthe\scurrent\sdb\sfile.\sTo\sdo\sthis\swe\shad\sto\sfall\sback\sto\snamed\sdbs,\sinstead\sof\sdefaulting\sto\san\sin-memory\sone,\sbut\sthe\svirtual\sfilesystem\sis\san\sin-memory\sFS,\sso\sthe\send\seffect\sis\sthe\ssame. -D 2022-05-24T22:16:12.220 +C fiddle:\srefactored\sso\sthat\sit\sno\slonger\sexposes\sany\sglobal\ssymbols.\sDoing\sso\swith\sthe\smain\ssqlite3.api\smodule\swill\sbe\smuch\stricker. +D 2022-05-25T03:08:22.772 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 -F Makefile.in dd31c34eb86a7869660f9697694d52c8f1c9705fede9717c59559530b6f0cb87 +F Makefile.in c2fc409d58889d2b4c6cfb28e28d9560d03b459edde2165863ca79742d4679e3 F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241 F Makefile.msc b28a8a7a977e7312f6859f560348e1eb110c21bd6cf9fab0d16537c0a514eef3 F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e @@ -57,13 +57,13 @@ F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaed F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72 F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 2f7c561af85e6d711fb42f395bc0074b6e6fcf16bc57d495ce4e1c3d0484c5d2 F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 540b9dec63a3a62a256e2f030827848a92e9b9d9b6fa5c0188295a4a1c5382cd -F ext/fiddle/EXPORTED_RUNTIME_METHODS ff64aea52779b0d4a838268275fe02adf6f2fdf4d9ce21c22d104bf3d7597398 +F ext/fiddle/EXPORTED_RUNTIME_METHODS 4808171d24c601e31d2ea4eb131180e25194d8e78a4f5b24797320c610201e26 F ext/fiddle/Makefile 2608fe0c56fa8f9cdf17e28d2be6def550a2fe987db5f7fc06d0210bfc868258 F ext/fiddle/SqliteTestUtil.js e3094833660a6ddd40766b802901b5861b37f0b89c6c577ee0ce4c9d36399e61 F ext/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f -F ext/fiddle/fiddle-worker.js 2c4e323ad5c94b863271d7779436a6e5291d3cc9dd2f5e5d067a22c2a539ff22 -F ext/fiddle/fiddle.html 468723b7a0bbdc92e24990c72e4b8bffdc1a8d605f91b595e36bcd8a6c840ff8 -F ext/fiddle/fiddle.js bc08897ceee8b4f32bb32dabab9c28d5aa1f252a88ac14b51933d6fa36e1c34c +F ext/fiddle/fiddle-worker.js ecfdf9d842f895046338469b30101f312866ca5dc3d3e002ae88b1256898094c +F ext/fiddle/fiddle.html 70796dc8a867448b41bc7e2c5fd6b1865ed8010b3abe22ba0678e8915c062e9a +F ext/fiddle/fiddle.js 931562304cf918b4b88c7095735e8b91896194c918a15afcfba0e5a31e273d63 F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf F ext/fiddle/sqlite3-api.js ce08520b8117e4fbbbeb02d8d047defd4e8507d687e76d20a39f12401bad0219 F ext/fiddle/testing-common.js a2527fd8dfb500bad9b434ae2645bb91489792115ee1e1b4b53cac4e9198992a @@ -1969,8 +1969,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 0fa8378c006fcf2311772d36cf2e3c2cd8e8648f671de89ee9832e2e1a06ef49 -R 39035a17b33485a10b3b4dd052e35f61 +P 7c7fd34c8a05832a3973aaffe696250cb4d2a0b1646c9bfbe83970daf33cd817 +R 51490a036ccbb69f6412426a9f5e3350 U stephan -Z 0fc30b16e829c9b67855feacd792a751 +Z 8dfdf2f1ba6221b5fdaef93177c62735 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index bb0697fefc..4f5e9539de 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7c7fd34c8a05832a3973aaffe696250cb4d2a0b1646c9bfbe83970daf33cd817 \ No newline at end of file +cd227be805d0cd4b6e3c72ed0992ad3aec3db9c366909d9d82c6d3a29009c6eb \ No newline at end of file -- 2.47.2