]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
fiddle: refactor into main thread (UI) and worker thread (wasm module). Added bits...
authorstephan <stephan@noemail.net>
Sat, 21 May 2022 14:19:05 +0000 (14:19 +0000)
committerstephan <stephan@noemail.net>
Sat, 21 May 2022 14:19:05 +0000 (14:19 +0000)
FossilOrigin-Name: 5ff3326856bc190cee15a5fca5ded89aacc4bf931a8df98726a872b310e2a4fc

Makefile.in
ext/fiddle/fiddle-worker.js [new file with mode: 0644]
ext/fiddle/fiddle.html [moved from ext/fiddle/fiddle.in.html with 88% similarity]
ext/fiddle/fiddle.js [new file with mode: 0644]
ext/fiddle/module-post.js [deleted file]
ext/fiddle/module-pre.js [deleted file]
manifest
manifest.uuid
src/shell.c.in

index a14c6a87eed793162376ce35ea1913495d844267..7a90bb5f0cadb459c70ecb8d8a79908e232f1fb7 100644 (file)
@@ -1519,9 +1519,9 @@ sqlite3.dll: $(REAL_LIBOBJ) sqlite3.def
 #
 fiddle_dir = ext/fiddle
 fiddle_html = $(fiddle_dir)/fiddle.html
-fiddle_generated = $(fiddle_html) \
-                   $(fiddle_dir)/fiddle.js \
-                   $(fiddle_dir)/fiddle.wasm
+fiddle_module_js = $(fiddle_dir)/fiddle-module.js
+fiddle_generated = $(fiddle_module_js) \
+                   $(fiddle_dir)/fiddle-module.wasm
 # fiddle_dummy_exports = ,comma,list of func bound solely for
 # experimentation and testing purposes in the WASM build.
 fiddle_dummy_exports = ,_fiddle_experiment,_fiddle_the_db,_fiddle_db_arg
@@ -1536,15 +1536,10 @@ clean: clean-fiddle
 emcc_opt = -Oz
 emcc_flags = $(emcc_opt) $(SHELL_OPT) \
              -sEXPORTED_RUNTIME_METHODS=ccall,cwrap \
-             -sEXPORTED_FUNCTIONS=_fiddle_exec$(fiddle_dummy_exports) \
-             --pre-js $(fiddle_dir)/module-pre.js \
-             --post-js $(fiddle_dir)/module-post.js \
-             --shell-file $(fiddle_dir)/fiddle.in.html \
+             -sEXPORTED_FUNCTIONS=_fiddle_exec,_fiddle_interrupt$(fiddle_dummy_exports) \
              $(fiddle_cflags)
 # $(fiddle_cflags) is intended to be passed to make via the CLI in
 # order to override, e.g., -Ox for one-off builds.
-$(fiddle_html): Makefile sqlite3.c shell.c \
-    $(fiddle_dir)/fiddle.in.html \
-    $(fiddle_dir)/module-pre.js $(fiddle_dir)/module-post.js
+$(fiddle_module_js): Makefile sqlite3.c shell.c
        emcc -o $@ $(emcc_flags) sqlite3.c shell.c
-fiddle: $(fiddle_html)
+fiddle: $(fiddle_module_js)
diff --git a/ext/fiddle/fiddle-worker.js b/ext/fiddle/fiddle-worker.js
new file mode 100644 (file)
index 0000000..5c6ed1d
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+  2022-05-20
+
+  The author disclaims copyright to this source code.  In place of a
+  legal notice, here is a blessing:
+
+  *   May you do good and not evil.
+  *   May you find forgiveness for yourself and forgive others.
+  *   May you share freely, never taking more than you give.
+
+  ***********************************************************************
+
+  This is the JS Worker file for the sqlite3 fiddle app. It loads the
+  sqlite3 wasm module and offers access to the db via the Worker
+  message-passing interface.
+
+  Because we can have only a single message handler, as opposed to an
+  arbitrary number of discrete event listeners like with DOM elements,
+  we have to define a lower-level message API. Messages abstractly
+  look like:
+
+  { type: string, data: type-specific value }
+
+  Where 'type' is used for dispatching and 'data' is a
+  'type'-dependent value.
+
+  The 'type' values expected by each side of the main/worker
+  connection vary. The types are described below but subject to
+  change at any time as this experiment evolves.
+
+  Workers-to-Main types
+
+  - stdout, stderr: indicate stdout/stderr output from the wasm
+    layer. The data property is the string of the output, noting
+    that the emscripten binding emits these one line at a time. Thus,
+    if a C-side puts() emits multiple lines in a single call, the JS
+    side will see that as multiple calls.
+
+  - module: Status text. This is intended to alert the main thread
+    about module loading status so that, e.g., the main thread can
+    update a progress widget and DTRT when the module is finished
+    loading and available for work. The status text is mostly in some
+    undocumented(?) format emited by the emscripten generated
+    module-loading code, encoding progress info within it.
+
+  - working: data='start'|'end'. Indicates that work is about to be
+    sent to the module or has just completed. This can be used, e.g.,
+    to disable UI elements which should not be activated while work
+    is pending.
+
+  Main-to-Worker types:
+
+  - shellExec: data=text to execute as if it had been entered in the
+    sqlite3 CLI shell app (as opposed to sqlite3_exec()). This event
+    causes the worker to emit a 'working' event (data='start') before
+    it starts and a 'working' event (data='end') when it finished. If
+    called while work is currently being executed it emits stderr
+    message instead of doing actual work, as the underlying db cannot
+    handle concurrent tasks.
+
+  - More TBD as the higher-level db layer develops.
+*/
+
+/*
+  Apparent browser(s) bug: console messages emitted may be duplicated
+  in the console, even though they're provably only run once. See:
+
+  https://stackoverflow.com/questions/49659464
+
+  Noting that it happens in Firefox as well as Chrome. Harmless but
+  annoying.
+*/
+
+const thisWorker = self;
+
+const wMsg = (type,data)=>postMessage({type, data});
+
+self.onerror = function(/*message, source, lineno, colno, error*/) {
+    const err = arguments[4];
+    if(err && 'ExitStatus'==err.name){
+        Module._isDead = true;
+        Module.printErr("FATAL ERROR:", err.message);
+        Module.printErr("Restarting the app requires reloading the page.");
+        //const taOutput = document.querySelector('#output');
+        //if(taOutput) taOutput.classList.add('error');
+    }
+    Module.setStatus('Exception thrown, see JavaScript console');
+    Module.setStatus = function(text) {
+        if(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));},
+    setStatus: function f(text){wMsg('module',{type:'status',data:text});},
+    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 shellExec = 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 {
+        wMsg('working','end');
+        delete f._running;
+    }
+};
+
+self.onmessage = function(ev){
+    ev = ev.data;
+    //console.debug("worker: onmessage.data",ev);
+    switch(ev.type){
+        case 'shellExec': shellExec(ev.data); return;
+    };
+    console.warn("Unknown fiddle-worker message type:",ev);
+};
+self.Module.setStatus('Downloading...');
+importScripts('fiddle-module.js')
+/* loads module and notifies, via Module.setStatus(), when it's done loading. */;
similarity index 88%
rename from ext/fiddle/fiddle.in.html
rename to ext/fiddle/fiddle.html
index e9edf09c495e4568a612f4e21e6bd057749a012f..3dce67439ef889cee9bb4f2c843e882461f919fa 100644 (file)
@@ -3,16 +3,16 @@
   <head>
     <meta charset="utf-8">
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <title>sqlite3 fiddle (experimental!)</title>
-    <!-- script src="jqterm/jqterm-bundle.min.js"></script>
-    <link rel="stylesheet" href="jqterm/jquery.terminal.min.css"/ -->
+    <title>sqlite3 fiddle</title>
+    <!--script src="jqterm/jqterm-bundle.min.js"></script>
+    <link rel="stylesheet" href="jqterm/jquery.terminal.min.css"/-->
     <style>
       /* emcscript-related styling, used during the intialization phase... */
       .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
       div.emscripten { text-align: center; }
       div.emscripten_border { border: 1px solid black; }
-      #spinner { overflow: visible; }
-      #spinner > * {
+      #module-spinner { overflow: visible; }
+      #module-spinner > * {
           margin-top: 1em;
       }
       .spinner {
   </head>
   <body>
     <header id='titlebar'><span>sqlite3 fiddle</span></header>
-    <figure id="spinner">
+    <figure id="module-spinner">
       <div class="spinner"></div>
       <div class='center'><strong>Initializing app...</strong></div>
       <div class='center'>
         failed and the JavaScript console may contain clues as to why.
       </div>
     </figure>
-    <div class="emscripten" id="status">Downloading...</div>
+    <div class="emscripten" id="module-status">Downloading...</div>
     <div class="emscripten">
-      <progress value="0" max="100" id="progress" hidden='1'></progress>  
+      <progress value="0" max="100" id="module-progress" hidden='1'></progress>  
     </div>
 
     <div id='view-terminal' class='app-view hidden initially-hidden'>
@@ -201,7 +201,7 @@ CREATE TABLE t(a,b);
 INSERT INTO t(a,b) VALUES('abc',123),('def',456),(NULL,789),('ghi',012);
 SELECT * FROM t;</textarea>        
           <div class='button-bar'>
-            <button id='btn-run'>Run</button>
+            <button id='btn-shell-exec'>Run</button>
             <button id='btn-clear'>Clear Input</button>
             <button data-cmd='.help'>Help</button>
             <select id='select-examples'></select>
@@ -218,18 +218,9 @@ SELECT * FROM t;</textarea>
     </div> <!-- #view-split -->
     <!-- Maintenance notes:
 
-        - emscripten module init goes is in module-pre.js and gets
-          prepended to the generated script code.
-
-        - App-specific code is in module-post.js and gets appended to
-          the generated script code.
-
-        - The following placeholder (if you're reading this in the
-          input template file) gets replaced by a generated
-          amalgamation of: module-pre.js, emcc-generated bootstrapping
-          code, and module-post.js.
+        ... TODO... currently being refactored...
 
     -->
-    {{{ SCRIPT }}}
+    <script src="fiddle.js"></script>
   </body>
 </html>
diff --git a/ext/fiddle/fiddle.js b/ext/fiddle/fiddle.js
new file mode 100644 (file)
index 0000000..d91f98a
--- /dev/null
@@ -0,0 +1,455 @@
+/*
+  2022-05-20
+
+  The author disclaims copyright to this source code.  In place of a
+  legal notice, here is a blessing:
+
+  *   May you do good and not evil.
+  *   May you find forgiveness for yourself and forgive others.
+  *   May you share freely, never taking more than you give.
+
+  ***********************************************************************
+
+  This is the main entry point for the sqlite3 fiddle app. It sets up the
+  various UI bits, loads a Worker for the db connection, and manages the
+  communication between the UI and worker.
+*/
+(function(){
+    'use strict';
+
+    /**
+       The SqliteFiddle object is intended to be the primary
+       app-level object for the main-thread side of the sqlite
+       fiddle application. It uses a worker thread to load the
+       sqlite WASM module and communicate with it.
+    */
+    const SF/*local convenience alias*/
+    = window.SqliteFiddle/*canonical name*/ = {
+        /* Config options. */
+        config: {
+            /* If true, SqliteFiddle.echo() will auto-scroll the
+               output widget to the bottom when it receives output,
+               else it won't. */
+            autoScrollOutput: true,
+            /* If true, the output area will be cleared before each
+               command is run, else it will not. */
+            autoClearOutput: false,
+            /* If true, SqliteFiddle.echo() will echo its output to
+               the console, in addition to its normal output widget.
+               That slows it down but is useful for testing. */
+            echoToConsole: false,
+            /* If true, display input/output areas side-by-side. */
+            sideBySide: false,
+            /* If true, swap positions of the input/output areas. */
+            swapInOut: false
+        },
+        /**
+           Emits the given text, followed by a line break, to the
+           output widget.  If given more than one argument, they are
+           join()'d together with a space between each. As a special
+           case, if passed a single array, that array is used in place
+           of the arguments array (this is to facilitate receiving
+           lists of arguments via worker events).
+        */
+        echo: function f(text) {
+            /* Maintenance reminder: we currently require/expect a textarea
+               output element. It might be nice to extend this to behave
+               differently if the output element is a non-textarea element,
+               in which case it would need to append the given text as a TEXT
+               node and add a line break. */
+            if(!f._){
+                f._ = document.getElementById('output');
+                f._.value = ''; // clear browser cache
+            }
+            if(arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
+            else if(1===arguments.length && Array.isArray(text)) text = text.join(' ');
+            // These replacements are necessary if you render to raw HTML
+            //text = text.replace(/&/g, "&amp;");
+            //text = text.replace(/</g, "&lt;");
+            //text = text.replace(/>/g, "&gt;");
+            //text = text.replace('\n', '<br>', 'g');
+            if(null===text){/*special case: clear output*/
+                f._.value = '';
+                return;
+            }else if(this.echo._clearPending){
+                delete this.echo._clearPending;
+                f._.value = '';
+            }
+            if(this.config.echoToConsole) console.log(text);
+            if(this.jqTerm) window.Module.jqTerm.echo(text);
+            f._.value += text + "\n";
+            if(this.config.autoScrollOutput){
+                f._.scrollTop = f._.scrollHeight;
+            }
+        },
+        _msgMap: {},
+        addMsgHandler: function f(type,callback){
+            if(Array.isArray(type)){
+                type.forEach((t)=>this.addMsgHandler(t, callback));
+                return this;
+            }
+            (this._msgMap.hasOwnProperty(type)
+             ? this._msgMap[type]
+             : (this._msgMap[type] = [])).push(callback);
+            return this;
+        },
+        runMsgHandlers: function(msg){
+            const list = (this._msgMap.hasOwnProperty(msg.type)
+                          ? this._msgMap[msg.type] : false);
+            if(!list){
+                console.warn("No handlers found for message type:",msg);
+                return false;
+            }
+            //console.debug("runMsgHandlers",msg);
+            list.forEach((f)=>f(msg));
+            return true;
+        },
+        clearMsgHandlers: function(type){
+            delete this._msgMap[type];
+            return this;
+        },
+        /* Posts a message in the form {type, data} to the db worker. Returns this. */
+        wMsg: function(type,data){
+            this.worker.postMessage({type, data});
+            return this;
+        }
+    };
+
+    SF.worker = new Worker('fiddle-worker.js');
+    SF.worker.onmessage = (ev)=>SF.runMsgHandlers(ev.data);
+    SF.addMsgHandler(['stdout', 'stderr'], (ev)=>SF.echo(ev.data));
+
+    /* querySelectorAll() proxy */
+    const EAll = function(/*[element=document,] cssSelector*/){
+        return (arguments.length>1 ? arguments[0] : document)
+            .querySelectorAll(arguments[arguments.length-1]);
+    };
+    /* querySelector() proxy */
+    const E = function(/*[element=document,] cssSelector*/){
+        return (arguments.length>1 ? arguments[0] : document)
+            .querySelector(arguments[arguments.length-1]);
+    };
+    
+    const statusElement = E('#module-status');
+    const progressElement = E('#module-progress');
+    const spinnerElement = E('#module-spinner');
+
+    SF.addMsgHandler('module', function f(ev){
+        ev = ev.data;
+        //console.log("Module status:",ev);
+        if('status'!==ev.type) return;
+        /* This weird handling of the ev.data is simply how
+           emscripten's auto-generated code notifies the client of
+           load progress. */
+        let text = ev.data;
+        if(!f.last) f.last = { time: Date.now(), text: '' };
+        if(text === f.last.text) return;
+        const m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
+        const now = Date.now();
+        if(m && now - f.last.time < 30) return; // if this is a progress update, skip it if too soon
+        f.last.time = now;
+        f.last.text = text;
+        if(m) {
+            text = m[1];
+            progressElement.value = parseInt(m[2])*100;
+            progressElement.max = parseInt(m[4])*100;
+            progressElement.hidden = false;
+            spinnerElement.hidden = false;
+        } else {
+            progressElement.remove();
+            if(!text) spinnerElement.remove();
+        }
+        if(text) statusElement.innerText = text;
+        else {
+            console.log("Finalizing status.");
+            statusElement.remove();
+            SF.clearMsgHandlers('module');
+            self.onSFLoaded();
+        }
+    });
+
+    /**
+       Performs all app initialization which must wait until after the
+       worker module is loaded. This function removes itself when it's
+       called.
+    */
+    self.onSFLoaded = function(){
+        delete this.onSFLoaded;
+        // Unhide all elements which start out hidden
+        EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden'));
+        
+        const taInput = E('#input');
+        const btnClearIn = E('#btn-clear');
+        btnClearIn.addEventListener('click',function(){
+            taInput.value = '';
+        },false);
+        // Ctrl-enter and shift-enter both run the current SQL.
+        taInput.addEventListener('keydown',function(ev){
+            if((ev.ctrlKey || ev.shiftKey) && 13 === ev.keyCode){
+                ev.preventDefault();
+                ev.stopPropagation();
+                btnShellExec.click();
+            }
+        }, false);
+        const taOutput = E('#output');
+        const btnClearOut = E('#btn-clear-output');
+        btnClearOut.addEventListener('click',function(){
+            taOutput.value = '';
+            if(SF.jqTerm) SF.jqTerm.clear();
+        },false);
+        const btnShellExec = E('#btn-shell-exec');
+        btnShellExec.addEventListener('click',function(){
+            const sql = taInput.value.trim();
+            if(sql) SF.dbExec(sql);
+        },false);
+
+        const preStartWork = function f(){
+            if(!f._){
+                const title = E('title');
+                f._ = {
+                    btnLabel: btnShellExec.innerText,
+                    pageTitle: title,
+                    pageTitleOrig: title.innerText
+                };
+            }
+            f._.pageTitle.innerText = "[working...] "+f._.pageTitleOrig;
+            btnShellExec.setAttribute('disabled','disabled');
+        };
+
+        /* Sends the given text to the db module to evaluate as if it
+           had been entered in the sqlite3 CLI shell. If it's null or
+           empty, this is a no-op except that the very first call will
+           initialize the db and output an informational header. */
+        SF.dbExec = function f(sql){
+            if(this.config.autoClearOutput){
+                this.echo._clearPending = true;
+            }
+            preStartWork();
+            this.wMsg('shellExec',sql);
+        };
+
+        SF.addMsgHandler('working',function f(ev){
+            if('start' === ev.data){
+                //btnShellExec.innerText = "Working..."; // forces layout reflow (annoying)
+            }else if('end' === ev.data){
+                preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig;
+                btnShellExec.innerText = preStartWork._.btnLabel;
+                btnShellExec.removeAttribute('disabled');
+            }
+        });
+
+        /* For each checkboxes with data-csstgt, set up a handler which
+           toggles the given CSS class on the element matching
+           E(data-csstgt). */
+        EAll('input[type=checkbox][data-csstgt]')
+            .forEach(function(e){
+                const tgt = E(e.dataset.csstgt);
+                const cssClass = e.dataset.cssclass || 'error';
+                e.checked = tgt.classList.contains(cssClass);
+                e.addEventListener('change', function(){
+                    tgt.classList[
+                        this.checked ? 'add' : 'remove'
+                    ](cssClass)
+                }, false);
+            });
+        /* For each checkbox with data-config=X, set up a binding to
+           SF.config[X]. These must be set up AFTER data-csstgt
+           checkboxes so that those two states can be synced properly. */
+        EAll('input[type=checkbox][data-config]')
+            .forEach(function(e){
+                const confVal = !!SF.config[e.dataset.config];
+                if(e.checked !== confVal){
+                    /* Ensure that data-csstgt mappings (if any) get
+                       synced properly. */
+                    e.checked = confVal;
+                    e.dispatchEvent(new Event('change'));
+                }
+                e.addEventListener('change', function(){
+                    SF.config[this.dataset.config] = this.checked;
+                }, false);
+            });
+        /* For each button with data-cmd=X, map a click handler which
+           calls dbExec(X). */
+        const cmdClick = function(){SF.dbExec(this.dataset.cmd);};
+        EAll('button[data-cmd]').forEach(
+            e => e.addEventListener('click', cmdClick, false)
+        );
+
+        /**
+           Given a DOM element, this routine measures its "effective
+           height", which is the bounding top/bottom range of this element
+           and all of its children, recursively. For some DOM structure
+           cases, a parent may have a reported height of 0 even though
+           children have non-0 sizes.
+
+           Returns 0 if !e or if the element really has no height.
+        */
+        const effectiveHeight = function f(e){
+            if(!e) return 0;
+            if(!f.measure){
+                f.measure = function callee(e, depth){
+                    if(!e) return;
+                    const m = e.getBoundingClientRect();
+                    if(0===depth){
+                        callee.top = m.top;
+                        callee.bottom = m.bottom;
+                    }else{
+                        callee.top = m.top ? Math.min(callee.top, m.top) : callee.top;
+                        callee.bottom = Math.max(callee.bottom, m.bottom);
+                    }
+                    Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1));
+                    if(0===depth){
+                        //console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top));
+                        f.extra += callee.bottom - callee.top;
+                    }
+                    return f.extra;
+                };
+            }
+            f.extra = 0;
+            f.measure(e,0);
+            return f.extra;
+        };
+
+        /**
+           Returns a function, that, as long as it continues to be invoked,
+           will not be triggered. The function will be called after it stops
+           being called for N milliseconds. If `immediate` is passed, call
+           the callback immediately and hinder future invocations until at
+           least the given time has passed.
+
+           If passed only 1 argument, or passed a falsy 2nd argument,
+           the default wait time set in this function's $defaultDelay
+           property is used.
+
+           Source: underscore.js, by way of https://davidwalsh.name/javascript-debounce-function
+        */
+        const debounce = function f(func, wait, immediate) {
+            var timeout;
+            if(!wait) wait = f.$defaultDelay;
+            return function() {
+                const context = this, args = Array.prototype.slice.call(arguments);
+                const later = function() {
+                    timeout = undefined;
+                    if(!immediate) func.apply(context, args);
+                };
+                const callNow = immediate && !timeout;
+                clearTimeout(timeout);
+                timeout = setTimeout(later, wait);
+                if(callNow) func.apply(context, args);
+            };
+        };
+        debounce.$defaultDelay = 500 /*arbitrary*/;
+
+        const ForceResizeKludge = (function(){
+            /* Workaround for Safari mayhem regarding use of vh CSS units....
+               We cannot use vh units to set the terminal area size because
+               Safari chokes on that, so we calculate that height here. Larger
+               than ~95% is too big for Firefox on Android, causing the input
+               area to move off-screen. */
+            const bcl = document.body.classList;
+            const appViews = EAll('.app-view');
+            const resized = function f(){
+                if(f.$disabled) return;
+                const wh = window.innerHeight;
+                var ht;
+                var extra = 0;
+                const elemsToCount = [
+                    E('body > header'),
+                    E('body > footer')
+                ];
+                elemsToCount.forEach((e)=>e ? extra += effectiveHeight(e) : false);
+                ht = wh - extra;
+                appViews.forEach(function(e){
+                    e.style.height =
+                        e.style.maxHeight = [
+                            "calc(", (ht>=100 ? ht : 100), "px",
+                            " - 2em"/*fudge value*/,")"
+                            /* ^^^^ hypothetically not needed, but both
+                               Chrome/FF on Linux will force scrollbars on the
+                               body if this value is too small. */
+                        ].join('');
+                });
+            };
+            resized.$disabled = true/*gets deleted when setup is finished*/;
+            window.addEventListener('resize', debounce(resized, 250), false);
+            return resized;
+        })();
+
+        /** Set up a selection list of examples */
+        (function(){
+            const xElem = E('#select-examples');
+            const examples = [
+                {name: "Timer on", sql: ".timer on"},
+                {name: "Setup table T", sql:`.nullvalue NULL
+CREATE TABLE t(a,b);
+INSERT INTO t(a,b) VALUES('abc',123),('def',456),(NULL,789),('ghi',012);
+SELECT * FROM t;`},
+                {name: "Table list", sql: ".tables"},
+                {name: "Box Mode", sql: ".mode box"},
+                {name: "JSON Mode", sql: ".mode json"},
+                {name: "Mandlebrot", sql: `WITH RECURSIVE
+  xaxis(x) AS (VALUES(-2.0) UNION ALL SELECT x+0.05 FROM xaxis WHERE x<1.2),
+  yaxis(y) AS (VALUES(-1.0) UNION ALL SELECT y+0.1 FROM yaxis WHERE y<1.0),
+  m(iter, cx, cy, x, y) AS (
+    SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis
+    UNION ALL
+    SELECT iter+1, cx, cy, x*x-y*y + cx, 2.0*x*y + cy FROM m 
+     WHERE (x*x + y*y) < 4.0 AND iter<28
+  ),
+  m2(iter, cx, cy) AS (
+    SELECT max(iter), cx, cy FROM m GROUP BY cx, cy
+  ),
+  a(t) AS (
+    SELECT group_concat( substr(' .+*#', 1+min(iter/7,4), 1), '') 
+    FROM m2 GROUP BY cy
+  )
+SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;`}
+            ];
+            const newOpt = function(lbl,val){
+                const o = document.createElement('option');
+                o.value = val;
+                if(!val) o.setAttribute('disabled',true);
+                o.appendChild(document.createTextNode(lbl));
+                xElem.appendChild(o);
+            };
+            newOpt("Examples (replaces input!)");
+            examples.forEach((o)=>newOpt(o.name, o.sql));
+            //xElem.setAttribute('disabled',true);
+            xElem.selectedIndex = 0;
+            xElem.addEventListener('change', function(){
+                taInput.value = '-- ' +
+                    this.selectedOptions[0].innerText +
+                    '\n' + this.value;
+                //dbExec(this.value);
+            });
+        })()/* example queries */;
+
+        SF.echo(null/*clear any output generated by the init process*/);
+        if(window.jQuery && window.jQuery.terminal){
+            /* Set up the terminal-style view... */
+            const eTerm = window.jQuery('#view-terminal').empty();
+            SF.jqTerm = eTerm.terminal(dbExec,{
+                prompt: 'sqlite> ',
+                greetings: false /* note that the docs incorrectly call this 'greeting' */
+            });
+            /* Set up a button to toggle the views... */
+            const head = E('header#titlebar');
+            const btnToggleView = document.createElement('button');
+            btnToggleView.appendChild(document.createTextNode("Toggle View"));
+            btnToggleView.addEventListener('click',function f(){
+                EAll('.app-view').forEach(e=>e.classList.toggle('hidden'));
+                if(document.body.classList.toggle('terminal-mode')){
+                    ForceResizeKludge();
+                }
+            }, false);
+            btnToggleView.click()/*default to terminal view*/;
+        }
+        SF.dbExec(null/*init the db and output the header*/);
+        SF.echo('This experimental app is provided in the hope that it',
+                'may prove interesting or useful but is not an officially',
+                'supported deliverable of the sqlite project. It is subject to',
+                'any number of changes or outright removal at any time.\n');
+        delete ForceResizeKludge.$disabled;
+        ForceResizeKludge();
+    }/*onSFLoaded()*/;
+})();
diff --git a/ext/fiddle/module-post.js b/ext/fiddle/module-post.js
deleted file mode 100644 (file)
index ec88563..0000000
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
-  2022-05-20
-
-  The author disclaims copyright to this source code.  In place of a
-  legal notice, here is a blessing:
-
-  *   May you do good and not evil.
-  *   May you find forgiveness for yourself and forgive others.
-  *   May you share freely, never taking more than you give.
-
-  ***********************************************************************
-
-  This is the --post-js file for emcc. It gets appended to the
-  generated fiddle.js. It should contain (or load) all app-level code.
-
-  Maintenance achtung: do not call any wasm-bound functions from
-  outside of the onRuntimeInitialized() function. They are not
-  permitted to be called until after the module init is complete,
-  which does not happen until after this file is processed. Once that
-  init is finished, Module.onRuntimeInitialized() will be
-  triggered. All app-level init code should go into that callback or
-  be triggered via it.  Calling wasm-bound functions before that
-  callback is run will trigger an assertion in the wasm environment.
-*/
-window.Module.onRuntimeInitialized = function(){
-    'use strict';
-    const Module = window.Module /* wasm module as set up by emscripten */;
-    delete Module.onRuntimeInitialized;
-
-    /* querySelectorAll() proxy */
-    const EAll = function(/*[element=document,] cssSelector*/){
-        return (arguments.length>1 ? arguments[0] : document)
-            .querySelectorAll(arguments[arguments.length-1]);
-    };
-    /* querySelector() proxy */
-    const E = function(/*[element=document,] cssSelector*/){
-        return (arguments.length>1 ? arguments[0] : document)
-            .querySelector(arguments[arguments.length-1]);
-    };
-    
-    // Unhide all elements which start out hidden
-    EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden'));
-    
-    const taInput = E('#input');
-    const btnClearIn = E('#btn-clear');
-    btnClearIn.addEventListener('click',function(){
-        taInput.value = '';
-    },false);
-    // Ctrl-enter and shift-enter both run the current SQL.
-    taInput.addEventListener('keydown',function(ev){
-        if((ev.ctrlKey || ev.shiftKey) && 13 === ev.keyCode){
-            ev.preventDefault();
-            ev.stopPropagation();
-            btnRun.click();
-        }
-    }, false);
-    const taOutput = E('#output');
-    const btnClearOut = E('#btn-clear-output');
-    btnClearOut.addEventListener('click',function(){
-        taOutput.value = '';
-        if(Module.jqTerm) Module.jqTerm.clear();
-    },false);
-    /* Sends the given text to the shell. If it's null or empty, this
-       is a no-op except that the very first call will initialize the
-       db and output an informational header. */
-    const doExec = function f(sql){
-        if(!f._) f._ = Module.cwrap('fiddle_exec', null, ['string']);
-        if(Module._isDead){
-            Module.printErr("shell module has exit()ed. Cannot run SQL.");
-            return;
-        }
-        if(Module.config.autoClearOutput) taOutput.value='';
-        f._(sql);
-    };
-    const btnRun = E('#btn-run');
-    btnRun.addEventListener('click',function(){
-        const sql = taInput.value.trim();
-        if(sql){
-            doExec(sql);
-        }
-    },false);
-
-    const mainWrapper = E('#main-wrapper');
-    /* For each checkboxes with data-csstgt, set up a handler which
-       toggles the given CSS class on the element matching
-       E(data-csstgt). */
-    EAll('input[type=checkbox][data-csstgt]')
-        .forEach(function(e){
-            const tgt = E(e.dataset.csstgt);
-            const cssClass = e.dataset.cssclass || 'error';
-            e.checked = tgt.classList.contains(cssClass);
-            e.addEventListener('change', function(){
-                tgt.classList[
-                    this.checked ? 'add' : 'remove'
-                ](cssClass)
-            }, false);
-        });
-    /* For each checkbox with data-config=X, set up a binding to
-       Module.config[X]. These must be set up AFTER data-csstgt
-       checkboxes so that those two states can be synced properly. */
-    EAll('input[type=checkbox][data-config]')
-        .forEach(function(e){
-            const confVal = !!Module.config[e.dataset.config];
-            if(e.checked !== confVal){
-                /* Ensure that data-csstgt mappings (if any) get
-                   synced properly. */
-                e.checked = confVal;
-                e.dispatchEvent(new Event('change'));
-            }
-            e.addEventListener('change', function(){
-                Module.config[this.dataset.config] = this.checked;
-            }, false);
-        });
-    /* For each button with data-cmd=X, map a click handler which
-       calls doExec(X). */
-    const cmdClick = function(){doExec(this.dataset.cmd);};
-    EAll('button[data-cmd]').forEach(
-        e => e.addEventListener('click', cmdClick, false)
-    );
-
-
-    /**
-       Given a DOM element, this routine measures its "effective
-       height", which is the bounding top/bottom range of this element
-       and all of its children, recursively. For some DOM structure
-       cases, a parent may have a reported height of 0 even though
-       children have non-0 sizes.
-
-       Returns 0 if !e or if the element really has no height.
-    */
-    const effectiveHeight = function f(e){
-        if(!e) return 0;
-        if(!f.measure){
-            f.measure = function callee(e, depth){
-                if(!e) return;
-                const m = e.getBoundingClientRect();
-                if(0===depth){
-                    callee.top = m.top;
-                    callee.bottom = m.bottom;
-                }else{
-                    callee.top = m.top ? Math.min(callee.top, m.top) : callee.top;
-                    callee.bottom = Math.max(callee.bottom, m.bottom);
-                }
-                Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1));
-                if(0===depth){
-                    //console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top));
-                    f.extra += callee.bottom - callee.top;
-                }
-                return f.extra;
-            };
-        }
-        f.extra = 0;
-        f.measure(e,0);
-        return f.extra;
-    };
-
-    /**
-       Returns a function, that, as long as it continues to be invoked,
-       will not be triggered. The function will be called after it stops
-       being called for N milliseconds. If `immediate` is passed, call
-       the callback immediately and hinder future invocations until at
-       least the given time has passed.
-
-       If passed only 1 argument, or passed a falsy 2nd argument,
-       the default wait time set in this function's $defaultDelay
-       property is used.
-
-       Source: underscore.js, by way of https://davidwalsh.name/javascript-debounce-function
-    */
-    const debounce = function f(func, wait, immediate) {
-        var timeout;
-        if(!wait) wait = f.$defaultDelay;
-        return function() {
-            const context = this, args = Array.prototype.slice.call(arguments);
-            const later = function() {
-                timeout = undefined;
-                if(!immediate) func.apply(context, args);
-            };
-            const callNow = immediate && !timeout;
-            clearTimeout(timeout);
-            timeout = setTimeout(later, wait);
-            if(callNow) func.apply(context, args);
-        };
-    };
-    debounce.$defaultDelay = 500 /*arbitrary*/;
-
-    const ForceResizeKludge = (function(){
-        /* Workaround for Safari mayhem regarding use of vh CSS units....
-           We cannot use vh units to set the terminal area size because
-           Safari chokes on that, so we calculate that height here. Larger
-           than ~95% is too big for Firefox on Android, causing the input
-           area to move off-screen. */
-        const bcl = document.body.classList;
-        const appViews = EAll('.app-view');
-        const resized = function f(){
-            if(f.$disabled) return;
-            const wh = window.innerHeight;
-            var ht;
-            var extra = 0;
-            const elemsToCount = [
-                E('body > header'),
-                E('body > footer')
-            ];
-            elemsToCount.forEach((e)=>e ? extra += effectiveHeight(e) : false);
-            ht = wh - extra;
-            appViews.forEach(function(e){
-                e.style.height =
-                e.style.maxHeight = [
-                    "calc(", (ht>=100 ? ht : 100), "px",
-                    " - 2em"/*fudge value*/,")"
-                    /* ^^^^ hypothetically not needed, but both
-                       Chrome/FF on Linux will force scrollbars on the
-                       body if this value is too small. */
-                ].join('');
-            });
-        };
-        resized.$disabled = true/*gets deleted when setup is finished*/;
-        window.addEventListener('resize', debounce(resized, 250), false);
-        return resized;
-    })();
-
-    /** Set up a selection list of examples */
-    (function(){
-        const xElem = E('#select-examples');
-        const examples = [
-            {name: "Timer on", sql: ".timer on"},
-            {name: "Setup table T", sql:`.nullvalue NULL
-CREATE TABLE t(a,b);
-INSERT INTO t(a,b) VALUES('abc',123),('def',456),(NULL,789),('ghi',012);
-SELECT * FROM t;`},
-            {name: "Table list", sql: ".tables"},
-            {name: "Box Mode", sql: ".mode box"},
-            {name: "JSON Mode", sql: ".mode json"},
-            {name: "Mandlebrot", sql: `WITH RECURSIVE
-  xaxis(x) AS (VALUES(-2.0) UNION ALL SELECT x+0.05 FROM xaxis WHERE x<1.2),
-  yaxis(y) AS (VALUES(-1.0) UNION ALL SELECT y+0.1 FROM yaxis WHERE y<1.0),
-  m(iter, cx, cy, x, y) AS (
-    SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis
-    UNION ALL
-    SELECT iter+1, cx, cy, x*x-y*y + cx, 2.0*x*y + cy FROM m 
-     WHERE (x*x + y*y) < 4.0 AND iter<28
-  ),
-  m2(iter, cx, cy) AS (
-    SELECT max(iter), cx, cy FROM m GROUP BY cx, cy
-  ),
-  a(t) AS (
-    SELECT group_concat( substr(' .+*#', 1+min(iter/7,4), 1), '') 
-    FROM m2 GROUP BY cy
-  )
-SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;`}
-        ];
-        const newOpt = function(lbl,val){
-            const o = document.createElement('option');
-            o.value = val;
-            if(!val) o.setAttribute('disabled',true);
-            o.appendChild(document.createTextNode(lbl));
-            xElem.appendChild(o);
-        };
-        newOpt("Examples (replaces input!)");
-        examples.forEach((o)=>newOpt(o.name, o.sql));
-        //xElem.setAttribute('disabled',true);
-        xElem.selectedIndex = 0;
-        xElem.addEventListener('change', function(){
-            taInput.value = '-- ' +
-                this.selectedOptions[0].innerText +
-                '\n' + this.value;
-            //doExec(this.value);
-        });
-    })()/* example queries */;
-
-    Module.print(null/*clear any output generated by the init process*/);
-    if(window.jQuery && window.jQuery.terminal){
-        /* Set up the terminal-style view... */
-        const eTerm = window.jQuery('#view-terminal').empty();
-        Module.jqTerm = eTerm.terminal(doExec,{
-            prompt: 'sqlite> ',
-            greetings: false /* note that the docs incorrectly call this 'greeting' */
-        });
-        /* Set up a button to toggle the views... */
-        const head = E('header#titlebar');
-        const btnToggleView = document.createElement('button');
-        btnToggleView.appendChild(document.createTextNode("Toggle View"));
-        btnToggleView.addEventListener('click',function f(){
-            EAll('.app-view').forEach(e=>e.classList.toggle('hidden'));
-            if(document.body.classList.toggle('terminal-mode')){
-                ForceResizeKludge();
-            }
-        }, false);
-        btnToggleView.click()/*default to terminal view*/;
-    }
-    doExec(null/*init the db and output the header*/);
-    Module.print('\nThis experimental app is provided in the hope that it',
-                 'may prove interesting or useful but is not an officially',
-                 'supported deliverable of the sqlite project. It is subject to',
-                 'any number of changes or outright removal at any time.\n');
-    delete ForceResizeKludge.$disabled;
-    ForceResizeKludge();
-};
diff --git a/ext/fiddle/module-pre.js b/ext/fiddle/module-pre.js
deleted file mode 100644 (file)
index 6a59c9e..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
-  2022-05-20
-
-  The author disclaims copyright to this source code.  In place of a
-  legal notice, here is a blessing:
-
-  *   May you do good and not evil.
-  *   May you find forgiveness for yourself and forgive others.
-  *   May you share freely, never taking more than you give.
-
-  ***********************************************************************
-
-  This is the --pre-js file for emcc. It gets prepended to the
-  generated fiddle.js. It should contain only code which is relevant
-  to the setup and initialization of the wasm module.
-*/
-(function(){
-    'use strict';
-
-    /**
-       What follows is part of the emscripten core setup. Do not
-       modify it without understanding what it's doing.
-    */
-    const statusElement = document.getElementById('status');
-    const progressElement = document.getElementById('progress');
-    const spinnerElement = document.getElementById('spinner');
-    const Module = window.Module = {
-        /* Config object. Referenced by certain Module methods and
-           app-level code. */
-        config: {
-            /* If true, the Module.print() impl will auto-scroll
-               the output widget to the bottom when it receives output,
-               else it won't. */
-            autoScrollOutput: true,
-            /* If true, the output area will be cleared before each
-               command is run, else it will not. */
-            autoClearOutput: false,
-            /* If true, Module.print() will echo its output to
-               the console, in addition to its normal output widget. */
-            printToConsole: true,
-            /* If true, display input/output areas side-by-side. */
-            sideBySide: false,
-            /* If true, swap positions of the input/output areas. */
-            swapInOut: false
-        },
-        preRun: [],
-        postRun: [],
-        //onRuntimeInitialized: function(){},
-        print: (function f() {
-            /* Maintenance reminder: we currently require/expect a textarea
-               output element. It might be nice to extend this to behave
-               differently if the output element is a non-textarea element,
-               in which case it would need to append the given text as a TEXT
-               node and add a line break. */
-            const outputElem = document.getElementById('output');
-            outputElem.value = ''; // clear browser cache
-            return function(text) {
-                if(arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
-                // These replacements are necessary if you render to raw HTML
-                //text = text.replace(/&/g, "&amp;");
-                //text = text.replace(/</g, "&lt;");
-                //text = text.replace(/>/g, "&gt;");
-                //text = text.replace('\n', '<br>', 'g');
-                if(null===text){/*special case: clear output*/
-                    outputElem.value = '';
-                    return;
-                }
-                if(window.Module.config.printToConsole) console.log(text);
-                if(window.Module.jqTerm) window.Module.jqTerm.echo(text);
-                outputElem.value += text + "\n";
-                if(window.Module.config.autoScrollOutput){
-                    outputElem.scrollTop = outputElem.scrollHeight;
-                }
-            };
-        })(),
-        setStatus: function f(text) {
-            if(!f.last) f.last = { time: Date.now(), text: '' };
-            if(text === f.last.text) return;
-            const m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
-            const now = Date.now();
-            if(m && now - f.last.time < 30) return; // if this is a progress update, skip it if too soon
-            f.last.time = now;
-            f.last.text = text;
-            if(m) {
-                text = m[1];
-                progressElement.value = parseInt(m[2])*100;
-                progressElement.max = parseInt(m[4])*100;
-                progressElement.hidden = false;
-                spinnerElement.hidden = false;
-            } else {
-                progressElement.remove();
-                if(!text) spinnerElement.remove();
-            }
-            if(text) statusElement.innerText = text;
-            else statusElement.remove();
-        },
-        totalDependencies: 0,
-        monitorRunDependencies: function(left) {
-            this.totalDependencies = Math.max(this.totalDependencies, left);
-            this.setStatus(left
-                           ? ('Preparing... (' + (this.totalDependencies-left)
-                              + '/' + this.totalDependencies + ')')
-                           : 'All downloads complete.');
-        }
-    };
-    Module.printErr = Module.print/*capture stderr output*/;
-    Module.setStatus('Downloading...');
-    window.onerror = function(/*message, source, lineno, colno, error*/) {
-        const err = arguments[4];
-        if(err && 'ExitStatus'==err.name){
-            Module._isDead = true;
-            Module.printErr("FATAL ERROR:", err.message);
-            Module.printErr("Restarting the app requires reloading the page.");
-            const taOutput = document.querySelector('#output');
-            if(taOutput) taOutput.classList.add('error');
-        }
-        Module.setStatus('Exception thrown, see JavaScript console');
-        spinnerElement.style.display = 'none';
-        Module.setStatus = function(text) {
-            if(text) console.error('[post-exception status] ' + text);
-        };
-    };
-})();
index ea11836e17d2b1ae9419fd07c7e073eb68c587fd..e97042e51b6953347bae21a75bc97be5eff95e66 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,9 +1,9 @@
-C fiddle:\sadd\sa\sselection\slist\sof\sexample\squeries.
-D 2022-05-21T00:45:46.764
+C fiddle:\srefactor\sinto\smain\sthread\s(UI)\sand\sworker\sthread\s(wasm\smodule).\sAdded\sbits\sneeded\sto\ssupport\striggering\ssqlite3_interrupt()\sbut\sdo\snot\syet\shave\sa\ssecond\sSharedWorker\sto\stest\sit\swith.
+D 2022-05-21T14:19:05.384
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
-F Makefile.in e3327ed68a4fd74f582db211b2762dda06d4a7f87220eaaaf97f63a200fdbefa
+F Makefile.in 91cf7d4ee6e97dd84cd5cdf541df498e2df038dd83f4b30d1ed8b19fbee3bbdd
 F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
 F Makefile.msc b28a8a7a977e7312f6859f560348e1eb110c21bd6cf9fab0d16537c0a514eef3
 F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e
@@ -56,10 +56,10 @@ F ext/expert/sqlite3expert.c 6ca30d73b9ed75bd56d6e0d7f2c962d2affaa72c505458619d0
 F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b
 F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72
 F ext/fiddle/Makefile b2904d52c10a7c984cfab95c54fb85f33aa8a6b2653faf1527d08ce57114be46
-F ext/fiddle/fiddle.in.html 15e20c0b235425e88dc622623146c8f44005ddd875e4843f1904823ad1ee16a3
+F ext/fiddle/fiddle-worker.js eae61419d5a7fcc9c4fcfda157ca0fe0a36b690b716899e5ec54a6d20c7037a9
+F ext/fiddle/fiddle.html f536878dbaa35ba4d9ad8c87dda7fb2ea5502fdd824577d83b2265d65b8ca4d1 w ext/fiddle/fiddle.in.html
+F ext/fiddle/fiddle.js 76a490d59bf0e09fb8a9610e6fa5c5032ee44680fbf9773b55974cb50d6dc3dc
 F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf
-F ext/fiddle/module-post.js ae25e1e0e77f3856d62798fe72aeae65614687ffa2a3d1f2be3949470c8bfe20
-F ext/fiddle/module-pre.js baee03024cff65ea70ae8d67116e9c0b44723217005e04400930ad4b48be51f4
 F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e
 F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b
 F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea
@@ -559,7 +559,7 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
 F src/resolve.c a4eb3c617027fd049b07432f3b942ea7151fa793a332a11a7d0f58c9539e104f
 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
 F src/select.c 74060a09f66c0c056f3c61627e22cb484af0bbfa29d7d14dcf17c684742c15de
-F src/shell.c.in c2d899a884cb0304cfd12e3d37c8152be5c578f8bed0e3dae53f92130363ccfc
+F src/shell.c.in 55d71bf8c7a8f2a66bc5f99cd76f226790b291599b83415533dad84a553ed806
 F src/sqlite.h.in d15c307939039086adca159dd340a94b79b69827e74c6d661f343eeeaefba896
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h a988810c9b21c0dc36dc7a62735012339dc76fc7ab448fb0792721d30eacb69d
@@ -1959,8 +1959,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 31706ef851f7d55e2fe865933ec03f42dfa8b49e6d1f12b6c016b5c1d3460504
-R e3bf69409e6eaf22474ba05290f0bc56
+P 74abf03977e1ff8c6043defa38211cdfcfbba1979771b90ed9d3dbc99750fe9f
+R 209182df1b3b6bd2044e6acab7e588b4
 U stephan
-Z 4e174f238744adc17a78f8f77017822d
+Z 8cd34b489ca85b418bdc02d766bcd2dc
 # Remove this line to create a well-formed Fossil manifest.
index 618407f4089255cb1c27bcf5998538c0a05b5897..06ad51f191e37c2831b924c468ac7b25c4d7be5f 100644 (file)
@@ -1 +1 @@
-74abf03977e1ff8c6043defa38211cdfcfbba1979771b90ed9d3dbc99750fe9f
\ No newline at end of file
+5ff3326856bc190cee15a5fca5ded89aacc4bf931a8df98726a872b310e2a4fc
\ No newline at end of file
index 8f3f8cdd7de6f6acb2339cb081071e182d14fe2f..83d985abf9a257938508b0f7f3adf59e864aa5fa 100644 (file)
@@ -12595,6 +12595,15 @@ sqlite3 * fiddle_db_arg(sqlite3 *arg){
     return arg;
 }
 
+/*
+** Intended to be called via a SharedWorker() while a separate
+** SharedWorker() (which manages the wasm module) is performing work
+** which should be interrupted.
+*/
+void fiddle_interrupt(void){
+  if(globalDb) sqlite3_interrupt(globalDb);
+}
+
 /*
 ** Trivial exportable function for emscripten. Needs to be exported using:
 **