aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm/fiddle/fiddle-worker.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/wasm/fiddle/fiddle-worker.js')
-rw-r--r--ext/wasm/fiddle/fiddle-worker.js415
1 files changed, 216 insertions, 199 deletions
diff --git a/ext/wasm/fiddle/fiddle-worker.js b/ext/wasm/fiddle/fiddle-worker.js
index ca562323c..fb9fd3022 100644
--- a/ext/wasm/fiddle/fiddle-worker.js
+++ b/ext/wasm/fiddle/fiddle-worker.js
@@ -89,213 +89,230 @@
*/
"use strict";
(function(){
+ /**
+ 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)
+ });
+ };
+
+ const stdout = function(){wMsg('stdout', Array.prototype.slice.call(arguments));};
+ const stderr = function(){wMsg('stderr', Array.prototype.slice.call(arguments));};
+
+ 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;
+ stderr("FATAL ERROR:", err.message);
+ stderr("Restarting the app requires reloading the page.");
+ wMsg('error', err);
+ }
+ console.error(err);
+ fiddleModule.setStatus('Exception thrown, see JavaScript console: '+err);
+ };
+
+ 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._();
+ },
/**
- Posts a message in the form {type,data} unless passed more than 2
- args, in which case it posts {type, data:[arg1...argN]}.
+ 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.
*/
- const wMsg = function(type,data){
- postMessage({
- type,
- data: arguments.length<3
- ? data
- : Array.prototype.slice.call(arguments,1)
- });
- };
-
- const stdout = function(){wMsg('stdout', Array.prototype.slice.call(arguments));};
- const stderr = function(){wMsg('stderr', Array.prototype.slice.call(arguments));};
-
- 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;
- stderr("FATAL ERROR:", err.message);
- stderr("Restarting the app requires reloading the page.");
- wMsg('error', err);
+ exec: function f(sql){
+ if(!f._) f._ = fiddleModule.cwrap('fiddle_exec', null, ['string']);
+ if(fiddleModule.isDead){
+ stderr("shell module has exit()ed. Cannot run SQL.");
+ return;
+ }
+ wMsg('working','start');
+ try {
+ if(f._running){
+ stderr('Cannot run multiple commands concurrently.');
+ }else{
+ f._running = true;
+ f._(sql);
}
- console.error(err);
- fiddleModule.setStatus('Exception thrown, see JavaScript console: '+err);
- };
-
- 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.
- */
- exec: function f(sql){
- if(!f._) f._ = fiddleModule.cwrap('fiddle_exec', null, ['string']);
- if(fiddleModule.isDead){
- stderr("shell module has exit()ed. Cannot run SQL.");
- return;
- }
- wMsg('working','start');
- try {
- if(f._running){
- stderr('Cannot run multiple commands concurrently.');
- }else{
- f._running = true;
- f._(sql);
+ } finally {
+ delete f._running;
+ wMsg('working','end');
+ }
+ },
+ resetDb: function f(){
+ if(!f._) f._ = fiddleModule.cwrap('fiddle_reset_db', null);
+ stdout("Resetting database.");
+ f._();
+ stdout("Reset",this.dbFilename());
+ },
+ /* 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);
+ stdout("Requesting interrupt.");
+ f._();
+ }
+ };
+
+ self.onmessage = function f(ev){
+ ev = ev.data;
+ if(!f.cache){
+ f.cache = {
+ prevFilename: null
+ };
+ }
+ //console.debug("worker: onmessage.data",ev);
+ switch(ev.type){
+ case 'shellExec': Sqlite3Shell.exec(ev.data); return;
+ case 'db-reset': Sqlite3Shell.resetDb(); 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.
}
- } finally {
- delete f._running;
- wMsg('working','end');
- }
- },
- resetDb: function f(){
- if(!f._) f._ = fiddleModule.cwrap('fiddle_reset_db', null);
- stdout("Resetting database.");
- f._();
- stdout("Reset",this.dbFilename());
- },
- /* 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);
- stdout("Requesting interrupt.");
- f._();
+ }
+ */
+ case 'db-export': {
+ const fn = Sqlite3Shell.dbFilename();
+ 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;
}
- };
-
- 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{
+ 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 && oldName !== fn){
+ try{fiddleModule.fsUnlink(oldName);}
+ catch(e){/*ignored*/}
+ }
+ stdout("Replaced DB with",fn+".");
+ return;
}
- //console.debug("worker: onmessage.data",ev);
- switch(ev.type){
- case 'shellExec': Sqlite3Shell.exec(ev.data); return;
- case 'db-reset': Sqlite3Shell.resetDb(); 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();
- 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;
- }
- 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{
- 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 && oldName !== fn){
- try{fiddleModule.FS.unlink(oldName);}
- catch(e){/*ignored*/}
- }
- stdout("Replaced DB with",fn+".");
- return;
- }
- };
- console.warn("Unknown fiddle-worker message type:",ev);
};
-
+ console.warn("Unknown fiddle-worker message type:",ev);
+ };
+
+ /**
+ emscripten module for use with build mode -sMODULARIZE.
+ */
+ const fiddleModule = {
+ print: stdout,
+ printErr: stderr,
/**
- emscripten module for use with build mode -sMODULARIZE.
+ Intercepts status updates from the emscripting module init
+ 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.
*/
- const fiddleModule = {
- print: stdout,
- printErr: stderr,
- /**
- Intercepts status updates from the emscripting module init
- 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}
- });
+ 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}
+ });
+ }
+ };
+
+ importScripts('fiddle-module.js');
+ /**
+ initFiddleModule() is installed via fiddle-module.js due to
+ building with:
+
+ emcc ... -sMODULARIZE=1 -sEXPORT_NAME=initFiddleModule
+ */
+ initFiddleModule(fiddleModule).then(function(thisModule){
+ fiddleModule.fsUnlink = fiddleModule.cwrap('sqlite3_wasm_vfs_unlink','number',['string']);
+ (function initOpfs(){
+ if(!self.FileSystemHandle || !self.FileSystemDirectoryHandle
+ || !self.FileSystemFileHandle){
+ stdout("OPFS unavailable. All DB state is transient.");
+ return;
+ }
+ try {
+ if(0===fiddleModule.ccall('sqlite3_wasm_init_opfs', undefined)){
+ stdout("Initialized OPFS WASMFS backend.");
+ }else{
+ stderr("Initialization of OPFS WASMFS backend failed.");
}
- };
-
- importScripts('fiddle-module.js');
- /**
- initFiddleModule() is installed via fiddle-module.js due to
- building with:
-
- emcc ... -sMODULARIZE=1 -sEXPORT_NAME=initFiddleModule
- */
- initFiddleModule(fiddleModule).then(function(thisModule){
- wMsg('fiddle-ready');
- });
+ }catch(e){
+ stderr("Apparently missing WASMFS:",e.message);
+ }
+ })();
+ wMsg('fiddle-ready');
+ });
})();