]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Refactoring towards getting fiddle to support OPFS as a first-class citizen. Certain...
authorstephan <stephan@noemail.net>
Sat, 24 Sep 2022 07:36:45 +0000 (07:36 +0000)
committerstephan <stephan@noemail.net>
Sat, 24 Sep 2022 07:36:45 +0000 (07:36 +0000)
FossilOrigin-Name: 1b923ed6438d7fef4508936e0c4bc026a368721698b1539961e3fb3140a185cb

ext/wasm/EXPORTED_FUNCTIONS.fiddle.in
ext/wasm/api/sqlite3-api-prologue.js
ext/wasm/common/whwasmutil.js
ext/wasm/fiddle/fiddle-worker.js
ext/wasm/fiddle/fiddle.js
manifest
manifest.uuid
src/shell.c.in

index b96ce4e67c2ace92a2b1735f4b73d4c026b133a1..392e36e842443e9d74c52a60ee04df31551a5542 100644 (file)
@@ -1,7 +1,8 @@
-_fiddle_exec
-_fiddle_interrupt
-_fiddle_experiment
-_fiddle_the_db
 _fiddle_db_arg
 _fiddle_db_filename
+_fiddle_exec
+_fiddle_experiment
+_fiddle_interrupt
+_fiddle_main
 _fiddle_reset_db
+_fiddle_the_db
index 1e2d387a15c2f3d611bea0568e8f57d023850bd7..639ad99c686f966e7417d31478c670c872308de7 100644 (file)
@@ -148,7 +148,11 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
       exports: undefined,
       memory: undefined,
       bigIntEnabled: (()=>{
-        if('undefined'!==typeof Module) return !!Module.HEAPU64;
+        if('undefined'!==typeof Module){
+          /* Emscripten module will contain HEAPU64 when build with
+             -sWASM_BIGINT=1, else it will not. */
+          return !!Module.HEAPU64;
+        }
         return !!self.BigInt64Array;
       })(),
       allocExportName: 'malloc',
@@ -771,7 +775,8 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
     return (p && name) ? name.startsWith(p+'/') : false;
   };
 
-  if(0===capi.wasm.exports.sqlite3_vfs_find(0)){
+  // This bit is highly arguable and is incompatible with the fiddle shell.
+  if(false && 0===capi.wasm.exports.sqlite3_vfs_find(0)){
     /* Assume that sqlite3_initialize() has not yet been called.
        This will be the case in an SQLITE_OS_KV build. */
     capi.wasm.exports.sqlite3_initialize();
index 46cdf84bb8cc254c9ee79f51f7b09fb15d03e0d8..e9ab8c5942fd1da7c8afa31b2a40338011cbc533 100644 (file)
@@ -1030,6 +1030,22 @@ self.WhWasmUtilInstaller = function(target){
     (jstr, returnWithLength=false)=>__allocCStr(jstr, returnWithLength,
                                                 target.scopedAlloc, 'scopedAllocCString()');
 
+  // impl for allocMainArgv() and scopedAllocMainArgv().
+  const __allocMainArgv = function(isScoped, list){
+    if(!list.length) toss("Cannot allocate empty array.");
+    const pList = target[
+      isScoped ? 'scopedAlloc' : 'alloc'
+    ](list.length * target.ptrSizeof);
+    let i = 0;
+    list.forEach((e)=>{
+      target.setPtrValue(pList + (target.ptrSizeof * i++),
+                         target[
+                           isScoped ? 'scopedAllocCString' : 'allocCString'
+                         ](""+e));
+    });
+    return pList;
+  };
+
   /**
      Creates an array, using scopedAlloc(), suitable for passing to a
      C-level main() routine. The input is a collection with a length
@@ -1042,16 +1058,13 @@ self.WhWasmUtilInstaller = function(target){
 
      Throws if list.length is falsy or scopedAllocPush() is not active.
   */
-  target.scopedAllocMainArgv = function(list){
-    if(!list.length) toss("Cannot allocate empty array.");
-    const pList = target.scopedAlloc(list.length * target.ptrSizeof);
-    let i = 0;
-    list.forEach((e)=>{
-      target.setPtrValue(pList + (target.ptrSizeof * i++),
-                         target.scopedAllocCString(""+e));
-    });
-    return pList;
-  };
+  target.scopedAllocMainArgv = (list)=>__allocMainArgv(true, list);
+
+  /**
+     Identical to scopedAllocMainArgv() but uses alloc() instead of
+     scopedAllocMainArgv
+  */
+  target.allocMainArgv = (list)=>__allocMainArgv(false, list);
 
   /**
      Wraps function call func() in a scopedAllocPush() and
index b76dcf212eeca16e8d564fb47e92bb8c99cb59a9..97774a1019b7d25e43fd6796c4541ebca93d12b4 100644 (file)
 
   const stdout = (...args)=>wMsg('stdout', args);
   const stderr = (...args)=>wMsg('stderr', args);
+
+  const fixmeOPFS = "(FIXME: won't work with vanilla OPFS.)";
   
   self.onerror = function(/*message, source, lineno, colno, error*/) {
     const err = arguments[4];
       if(!f._) f._ = fiddleModule.cwrap('fiddle_db_filename', "string", ['string']);
       return f._();
     },
+    runMain: function f(){
+      if(f.argv) return f.argv.rc;
+      const dbName = "/fiddle.sqlite3";
+      f.argv = [
+        'sqlite3-fiddle.wasm',
+        '-bail', '-safe',
+        dbName
+        /* Reminder: because of how we run fiddle, we have to ensure
+           that any argv strings passed to its main() are valid until
+           the wasm environment shuts down. */
+      ];
+      const capi = fiddleModule.sqlite3.capi;
+      f.argv.pArgv = capi.wasm.allocMainArgv(f.argv);
+      f.argv.rc = capi.wasm.exports.fiddle_main(
+        f.argv.length, f.argv.pArgv
+      );
+      if(f.argv.rc){
+        stderr("Fatal error initializing sqlite3 shell.");
+        fiddleModule.isDead = true;
+        return false;
+      }
+      stdout("SQLite version", capi.sqlite3_libversion(),
+             capi.sqlite3_sourceid().substr(0,19));
+      stdout('Welcome to the "fiddle" shell');
+      stdout('Enter ".help" for usage hints.');
+      return true;
+    },
     /**
        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(!f._){
+        if(!this.runMain()) return;
+        f._ = fiddleModule.cwrap('fiddle_exec', null, ['string']);
+      }
       if(fiddleModule.isDead){
         stderr("shell module has exit()ed. Cannot run SQL.");
         return;
       try {
         if(f._running){
           stderr('Cannot run multiple commands concurrently.');
-        }else{
+        }else if(sql){
           f._running = true;
           f._(sql);
         }
-      } finally {
+      }finally{
         delete f._running;
         wMsg('working','end');
       }
     },
     resetDb: function f(){
       if(!f._) f._ = fiddleModule.cwrap('fiddle_reset_db', null);
-      stdout("Resetting database.");
+      stdout("Resetting database.",fixmeOPFS);
       f._();
       stdout("Reset",this.dbFilename());
     },
           */
         case 'db-export': {
           const fn = Sqlite3Shell.dbFilename();
-          stdout("Exporting",fn+".");
+          stdout("Exporting",fn+".",fixmeOPFS);
           const fn2 = fn ? fn.split(/[/\\]/).pop() : null;
           try{
             if(!fn2) throw new Error("DB appears to be closed.");
   initFiddleModule(fiddleModule).then(function(thisModule){
     const S = thisModule.sqlite3;
     const atEnd = ()=>{
-      thisModule.fsUnlink = thisModule.cwrap('sqlite3_wasm_vfs_unlink','number',['string']);
+      thisModule.fsUnlink = function(fn){
+        stderr("unlink:",fixmeOPFS);
+        return thisModule.ccall('sqlite3_wasm_vfs_unlink','number',['string']);
+      };
       wMsg('fiddle-ready');
     };
-    if(0){
+    if(1){
       S.installOpfsVfs().finally(function(){
-        if(S.opfs){
-          stdout("OPFS is available.");
-        }
+        if(S.opfs) stdout("OPFS is available.");
         atEnd();
       });
     }else{
       atEnd();
     }
-  });
+  })/*then()*/;
 })();
index b971233ef9acb1ff09611e7768bb465f2ebb9110..0d8792d55d3c56dcc6b2fe836717c941b354a28c 100644 (file)
   communication between the UI and worker.
 */
 (function(){
+  'use strict';
+  /* Recall that the 'self' symbol, except where locally
+     overwritten, refers to the global window or worker object. */
+
+  const storage = (function(NS/*namespace object in which to store this module*/){
+    /* Pedantic licensing note: this code originated in the Fossil SCM
+       source tree, where it has a different license, but the person who
+       ported it into sqlite is the same one who wrote it for fossil. */
     'use strict';
-    /* Recall that the 'self' symbol, except where locally
-       overwritten, refers to the global window or worker object. */
-
-    const storage = (function(NS/*namespace object in which to store this module*/){
-        /* Pedantic licensing note: this code originated in the Fossil SCM
-           source tree, where it has a different license, but the person who
-           ported it into sqlite is the same one who wrote it for fossil. */
-        'use strict';
-        NS = NS||{};
-
-        /**
-           This module provides a basic wrapper around localStorage
-           or sessionStorage or a dummy proxy object if neither
-           of those are available.
-        */
-        const tryStorage = function f(obj){
-            if(!f.key) f.key = 'storage.access.check';
-            try{
-                obj.setItem(f.key, 'f');
-                const x = obj.getItem(f.key);
-                obj.removeItem(f.key);
-                if(x!=='f') throw new Error(f.key+" failed")
-                return obj;
-            }catch(e){
-                return undefined;
-            }
-        };
+    NS = NS||{};
 
-        /** Internal storage impl for this module. */
-        const $storage =
-              tryStorage(window.localStorage)
-              || tryStorage(window.sessionStorage)
-              || tryStorage({
-                  // A basic dummy xyzStorage stand-in
-                  $$$:{},
-                  setItem: function(k,v){this.$$$[k]=v},
-                  getItem: function(k){
-                      return this.$$$.hasOwnProperty(k) ? this.$$$[k] : undefined;
-                  },
-                  removeItem: function(k){delete this.$$$[k]},
-                  clear: function(){this.$$$={}}
-              });
-
-        /**
-           For the dummy storage we need to differentiate between
-           $storage and its real property storage for hasOwnProperty()
-           to work properly...
-        */
-        const $storageHolder = $storage.hasOwnProperty('$$$') ? $storage.$$$ : $storage;
-
-        /**
-           A prefix which gets internally applied to all storage module
-           property keys so that localStorage and sessionStorage across the
-           same browser profile instance do not "leak" across multiple apps
-           being hosted by the same origin server. Such cross-polination is
-           still there but, with this key prefix applied, it won't be
-           immediately visible via the storage API.
-
-           With this in place we can justify using localStorage instead of
-           sessionStorage.
-
-           One implication of using localStorage and sessionStorage is that
-           their scope (the same "origin" and client application/profile)
-           allows multiple apps on the same origin to use the same
-           storage. Thus /appA/foo could then see changes made via
-           /appB/foo. The data do not cross user- or browser boundaries,
-           though, so it "might" arguably be called a
-           feature. storageKeyPrefix was added so that we can sandbox that
-           state for each separate app which shares an origin.
-
-           See: https://fossil-scm.org/forum/forumpost/4afc4d34de
-
-           Sidebar: it might seem odd to provide a key prefix and stick all
-           properties in the topmost level of the storage object. We do that
-           because adding a layer of object to sandbox each app would mean
-           (de)serializing that whole tree on every storage property change.
-           e.g. instead of storageObject.projectName.foo we have
-           storageObject[storageKeyPrefix+'foo']. That's soley for
-           efficiency's sake (in terms of battery life and
-           environment-internal storage-level effort).
-        */
-        const storageKeyPrefix = (
-            $storageHolder===$storage/*localStorage or sessionStorage*/
-                ? (
-                    (NS.config ?
-                     (NS.config.projectCode || NS.config.projectName
-                      || NS.config.shortProjectName)
-                     : false)
-                        || window.location.pathname
-                )+'::' : (
-                    '' /* transient storage */
-                )
-        );
-
-        /**
-           A proxy for localStorage or sessionStorage or a
-           page-instance-local proxy, if neither one is availble.
-
-           Which exact storage implementation is uses is unspecified, and
-           apps must not rely on it.
-        */
-        NS.storage = {
-            storageKeyPrefix: storageKeyPrefix,
-            /** Sets the storage key k to value v, implicitly converting
-                it to a string. */
-            set: (k,v)=>$storage.setItem(storageKeyPrefix+k,v),
-            /** Sets storage key k to JSON.stringify(v). */
-            setJSON: (k,v)=>$storage.setItem(storageKeyPrefix+k,JSON.stringify(v)),
-            /** Returns the value for the given storage key, or
-                dflt if the key is not found in the storage. */
-            get: (k,dflt)=>$storageHolder.hasOwnProperty(
-                storageKeyPrefix+k
-            ) ? $storage.getItem(storageKeyPrefix+k) : dflt,
-            /** Returns true if the given key has a value of "true".  If the
-                key is not found, it returns true if the boolean value of dflt
-                is "true". (Remember that JS persistent storage values are all
-                strings.) */
-            getBool: function(k,dflt){
-                return 'true'===this.get(k,''+(!!dflt));
-            },
-            /** Returns the JSON.parse()'d value of the given
-                storage key's value, or dflt is the key is not
-                found or JSON.parse() fails. */
-            getJSON: function f(k,dflt){
-                try {
-                    const x = this.get(k,f);
-                    return x===f ? dflt : JSON.parse(x);
-                }
-                catch(e){return dflt}
-            },
-            /** Returns true if the storage contains the given key,
-                else false. */
-            contains: (k)=>$storageHolder.hasOwnProperty(storageKeyPrefix+k),
-            /** Removes the given key from the storage. Returns this. */
-            remove: function(k){
-                $storage.removeItem(storageKeyPrefix+k);
-                return this;
-            },
-            /** Clears ALL keys from the storage. Returns this. */
-            clear: function(){
-                this.keys().forEach((k)=>$storage.removeItem(/*w/o prefix*/k));
-                return this;
-            },
-            /** Returns an array of all keys currently in the storage. */
-            keys: ()=>Object.keys($storageHolder).filter((v)=>(v||'').startsWith(storageKeyPrefix)),
-            /** Returns true if this storage is transient (only available
-                until the page is reloaded), indicating that fileStorage
-                and sessionStorage are unavailable. */
-            isTransient: ()=>$storageHolder!==$storage,
-            /** Returns a symbolic name for the current storage mechanism. */
-            storageImplName: function(){
-                if($storage===window.localStorage) return 'localStorage';
-                else if($storage===window.sessionStorage) return 'sessionStorage';
-                else return 'transient';
-            },
+    /**
+       This module provides a basic wrapper around localStorage
+       or sessionStorage or a dummy proxy object if neither
+       of those are available.
+    */
+    const tryStorage = function f(obj){
+      if(!f.key) f.key = 'storage.access.check';
+      try{
+        obj.setItem(f.key, 'f');
+        const x = obj.getItem(f.key);
+        obj.removeItem(f.key);
+        if(x!=='f') throw new Error(f.key+" failed")
+        return obj;
+      }catch(e){
+        return undefined;
+      }
+    };
 
-            /**
-               Returns a brief help text string for the currently-selected
-               storage type.
-            */
-            storageHelpDescription: function(){
-                return {
-                    localStorage: "Browser-local persistent storage with an "+
-                        "unspecified long-term lifetime (survives closing the browser, "+
-                        "but maybe not a browser upgrade).",
-                    sessionStorage: "Storage local to this browser tab, "+
-                        "lost if this tab is closed.",
-                    "transient": "Transient storage local to this invocation of this page."
-                }[this.storageImplName()];
-            }
-        };
-        return NS.storage;
-    })({})/*storage API setup*/;
+    /** Internal storage impl for this module. */
+    const $storage =
+          tryStorage(window.localStorage)
+          || tryStorage(window.sessionStorage)
+          || tryStorage({
+            // A basic dummy xyzStorage stand-in
+            $$$:{},
+            setItem: function(k,v){this.$$$[k]=v},
+            getItem: function(k){
+              return this.$$$.hasOwnProperty(k) ? this.$$$[k] : undefined;
+            },
+            removeItem: function(k){delete this.$$$[k]},
+            clear: function(){this.$$$={}}
+          });
 
+    /**
+       For the dummy storage we need to differentiate between
+       $storage and its real property storage for hasOwnProperty()
+       to work properly...
+    */
+    const $storageHolder = $storage.hasOwnProperty('$$$') ? $storage.$$$ : $storage;
 
-    /** Name of the stored copy of SqliteFiddle.config. */
-    const configStorageKey = 'sqlite3-fiddle-config';
+    /**
+       A prefix which gets internally applied to all storage module
+       property keys so that localStorage and sessionStorage across the
+       same browser profile instance do not "leak" across multiple apps
+       being hosted by the same origin server. Such cross-polination is
+       still there but, with this key prefix applied, it won't be
+       immediately visible via the storage API.
+
+       With this in place we can justify using localStorage instead of
+       sessionStorage.
+
+       One implication of using localStorage and sessionStorage is that
+       their scope (the same "origin" and client application/profile)
+       allows multiple apps on the same origin to use the same
+       storage. Thus /appA/foo could then see changes made via
+       /appB/foo. The data do not cross user- or browser boundaries,
+       though, so it "might" arguably be called a
+       feature. storageKeyPrefix was added so that we can sandbox that
+       state for each separate app which shares an origin.
+
+       See: https://fossil-scm.org/forum/forumpost/4afc4d34de
+
+       Sidebar: it might seem odd to provide a key prefix and stick all
+       properties in the topmost level of the storage object. We do that
+       because adding a layer of object to sandbox each app would mean
+       (de)serializing that whole tree on every storage property change.
+       e.g. instead of storageObject.projectName.foo we have
+       storageObject[storageKeyPrefix+'foo']. That's soley for
+       efficiency's sake (in terms of battery life and
+       environment-internal storage-level effort).
+    */
+    const storageKeyPrefix = (
+      $storageHolder===$storage/*localStorage or sessionStorage*/
+        ? (
+          (NS.config ?
+           (NS.config.projectCode || NS.config.projectName
+            || NS.config.shortProjectName)
+           : false)
+            || window.location.pathname
+        )+'::' : (
+          '' /* transient storage */
+        )
+    );
 
     /**
-       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.
+       A proxy for localStorage or sessionStorage or a
+       page-instance-local proxy, if neither one is availble.
+
+       Which exact storage implementation is uses is unspecified, and
+       apps must not rely on it.
     */
-    const SF/*local convenience alias*/
-    = window.SqliteFiddle/*canonical name*/ = {
-        /* Config options. */
-        config: {
+    NS.storage = {
+      storageKeyPrefix: storageKeyPrefix,
+      /** Sets the storage key k to value v, implicitly converting
+          it to a string. */
+      set: (k,v)=>$storage.setItem(storageKeyPrefix+k,v),
+      /** Sets storage key k to JSON.stringify(v). */
+      setJSON: (k,v)=>$storage.setItem(storageKeyPrefix+k,JSON.stringify(v)),
+      /** Returns the value for the given storage key, or
+          dflt if the key is not found in the storage. */
+      get: (k,dflt)=>$storageHolder.hasOwnProperty(
+        storageKeyPrefix+k
+      ) ? $storage.getItem(storageKeyPrefix+k) : dflt,
+      /** Returns true if the given key has a value of "true".  If the
+          key is not found, it returns true if the boolean value of dflt
+          is "true". (Remember that JS persistent storage values are all
+          strings.) */
+      getBool: function(k,dflt){
+        return 'true'===this.get(k,''+(!!dflt));
+      },
+      /** Returns the JSON.parse()'d value of the given
+          storage key's value, or dflt is the key is not
+          found or JSON.parse() fails. */
+      getJSON: function f(k,dflt){
+        try {
+          const x = this.get(k,f);
+          return x===f ? dflt : JSON.parse(x);
+        }
+        catch(e){return dflt}
+      },
+      /** Returns true if the storage contains the given key,
+          else false. */
+      contains: (k)=>$storageHolder.hasOwnProperty(storageKeyPrefix+k),
+      /** Removes the given key from the storage. Returns this. */
+      remove: function(k){
+        $storage.removeItem(storageKeyPrefix+k);
+        return this;
+      },
+      /** Clears ALL keys from the storage. Returns this. */
+      clear: function(){
+        this.keys().forEach((k)=>$storage.removeItem(/*w/o prefix*/k));
+        return this;
+      },
+      /** Returns an array of all keys currently in the storage. */
+      keys: ()=>Object.keys($storageHolder).filter((v)=>(v||'').startsWith(storageKeyPrefix)),
+      /** Returns true if this storage is transient (only available
+          until the page is reloaded), indicating that fileStorage
+          and sessionStorage are unavailable. */
+      isTransient: ()=>$storageHolder!==$storage,
+      /** Returns a symbolic name for the current storage mechanism. */
+      storageImplName: function(){
+        if($storage===window.localStorage) return 'localStorage';
+        else if($storage===window.sessionStorage) return 'sessionStorage';
+        else return 'transient';
+      },
+
+      /**
+         Returns a brief help text string for the currently-selected
+         storage type.
+      */
+      storageHelpDescription: function(){
+        return {
+          localStorage: "Browser-local persistent storage with an "+
+            "unspecified long-term lifetime (survives closing the browser, "+
+            "but maybe not a browser upgrade).",
+          sessionStorage: "Storage local to this browser tab, "+
+            "lost if this tab is closed.",
+          "transient": "Transient storage local to this invocation of this page."
+        }[this.storageImplName()];
+      }
+    };
+    return NS.storage;
+  })({})/*storage API setup*/;
+
+
+  /** Name of the stored copy of SqliteFiddle.config. */
+  const configStorageKey = 'sqlite3-fiddle-config';
+
+  /**
+     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. */
             sideBySide: true,
             /* 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) {
+          },
+          /**
+             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
+              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(' ');
             //text = text.replace(/>/g, "&gt;");
             //text = text.replace('\n', '<br>', 'g');
             if(null===text){/*special case: clear output*/
-                f._.value = '';
-                return;
+              f._.value = '';
+              return;
             }else if(this.echo._clearPending){
-                delete this.echo._clearPending;
-                f._.value = '';
+              delete this.echo._clearPending;
+              f._.value = '';
             }
             if(this.config.echoToConsole) console.log(text);
             if(this.jqTerm) this.jqTerm.echo(text);
             f._.value += text + "\n";
             if(this.config.autoScrollOutput){
-                f._.scrollTop = f._.scrollHeight;
+              f._.scrollTop = f._.scrollHeight;
             }
-        },
-        _msgMap: {},
-        /** Adds a worker message handler for messages of the given
-            type. */
-        addMsgHandler: function f(type,callback){
+          },
+          _msgMap: {},
+          /** Adds a worker message handler for messages of the given
+              type. */
+          addMsgHandler: function f(type,callback){
             if(Array.isArray(type)){
-                type.forEach((t)=>this.addMsgHandler(t, callback));
-                return this;
+              type.forEach((t)=>this.addMsgHandler(t, callback));
+              return this;
             }
             (this._msgMap.hasOwnProperty(type)
              ? this._msgMap[type]
              : (this._msgMap[type] = [])).push(callback);
             return this;
-        },
-        /** Given a worker message, runs all handlers for msg.type. */
-        runMsgHandlers: function(msg){
+          },
+          /** Given a worker message, runs all handlers for msg.type. */
+          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.warn("No handlers found for message type:",msg);
+              return false;
             }
             //console.debug("runMsgHandlers",msg);
             list.forEach((f)=>f(msg));
             return true;
-        },
-        /** Removes all message handlers for the given message type. */
-        clearMsgHandlers: function(type){
+          },
+          /** Removes all message handlers for the given message type. */
+          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){
+          },
+          /* 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;
-        },
-        /**
-           Prompts for confirmation and, if accepted, deletes
-           all content and tables in the (transient) database.
-        */
-        resetDb: function(){
+          },
+          /**
+             Prompts for confirmation and, if accepted, deletes
+             all content and tables in the (transient) database.
+          */
+          resetDb: function(){
             if(window.confirm("Really destroy all content and tables "
                               +"in the (transient) db?")){
-                this.wMsg('db-reset');
+              this.wMsg('db-reset');
             }
             return this;
-        },
-        /** Stores this object's config in the browser's storage. */
-        storeConfig: function(){
+          },
+          /** Stores this object's config in the browser's storage. */
+          storeConfig: function(){
             storage.setJSON(configStorageKey,this.config);
-        }
-    };
+          }
+        };
 
-    if(1){ /* Restore SF.config */
-        const storedConfig = storage.getJSON(configStorageKey);
-        if(storedConfig){
-            /* Copy all properties to SF.config which are currently in
-               storedConfig. We don't bother copying any other
-               properties: those have been removed from the app in the
-               meantime. */
-            Object.keys(SF.config).forEach(function(k){
-                if(storedConfig.hasOwnProperty(k)){
-                    SF.config[k] = storedConfig[k];
-                }
-            });
+  if(1){ /* Restore SF.config */
+    const storedConfig = storage.getJSON(configStorageKey);
+    if(storedConfig){
+      /* Copy all properties to SF.config which are currently in
+         storedConfig. We don't bother copying any other
+         properties: those have been removed from the app in the
+         meantime. */
+      Object.keys(SF.config).forEach(function(k){
+        if(storedConfig.hasOwnProperty(k)){
+          SF.config[k] = storedConfig[k];
         }
+      });
     }
-
-    SF.worker = new Worker('fiddle-worker.js'+self.location.search);
-    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]);
+  }
+
+  SF.worker = new Worker('fiddle-worker.js'+self.location.search);
+  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]);
+  };
+
+  /** Handles status updates from the Module object. */
+  SF.addMsgHandler('module', function f(ev){
+    ev = ev.data;
+    if('status'!==ev.type){
+      console.warn("Unexpected module-type message:",ev);
+      return;
+    }
+    if(!f.ui){
+      f.ui = {
+        status: E('#module-status'),
+        progress: E('#module-progress'),
+        spinner: E('#module-spinner')
+      };
+    }
+    const msg = ev.data;
+    if(f.ui.progres){
+      progress.value = msg.step;
+      progress.max = msg.step + 1/*we don't know how many steps to expect*/;
+    }
+    if(1==msg.step){
+      f.ui.progress.classList.remove('hidden');
+      f.ui.spinner.classList.remove('hidden');
+    }
+    if(msg.text){
+      f.ui.status.classList.remove('hidden');
+      f.ui.status.innerText = msg.text;
+    }else{
+      if(f.ui.progress){
+        f.ui.progress.remove();
+        f.ui.spinner.remove();
+        delete f.ui.progress;
+        delete f.ui.spinner;
+      }
+      f.ui.status.classList.add('hidden');
+      /* The module can post messages about fatal problems,
+         e.g. an exit() being triggered or assertion failure,
+         after the last "load" message has arrived, so
+         leave f.ui.status and message listener intact. */
+    }
+  });
+
+  /**
+     The 'fiddle-ready' event is fired (with no payload) when the
+     wasm module has finished loading. Interestingly, that happens
+     _before_ the final module:status event */
+  SF.addMsgHandler('fiddle-ready', function(){
+    SF.clearMsgHandlers('fiddle-ready');
+    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'));
+    E('#btn-reset').addEventListener('click',()=>SF.resetDb());
+    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(ev){
+      let sql;
+      ev.preventDefault();
+      if(taInput.selectionStart<taInput.selectionEnd){
+        sql = taInput.value.substring(taInput.selectionStart,taInput.selectionEnd).trim();
+      }else{
+        sql = taInput.value.trim();
+      }
+      if(sql) SF.dbExec(sql);
+    },false);
+
+    const btnInterrupt = E("#btn-interrupt");
+    //btnInterrupt.classList.add('hidden');
+    /** To be called immediately before work is sent to the
+        worker. Updates some UI elements. The 'working'/'end'
+        event will apply the inverse, undoing the bits this
+        function does. This impl is not in the 'working'/'start'
+        event handler because that event is given to us
+        asynchronously _after_ we need to have performed this
+        work.
+    */
+    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');
+      btnInterrupt.removeAttribute('disabled','disabled');
     };
-    /* querySelector() proxy */
-    const E = function(/*[element=document,] cssSelector*/){
-        return (arguments.length>1 ? arguments[0] : document)
-            .querySelector(arguments[arguments.length-1]);
+
+    /* 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. */
+    SF.dbExec = function f(sql){
+      if(null!==sql && this.config.autoClearOutput){
+        this.echo._clearPending = true;
+      }
+      preStartWork();
+      this.wMsg('shellExec',sql);
     };
 
-    /** Handles status updates from the Module object. */
-    SF.addMsgHandler('module', function f(ev){
-        ev = ev.data;
-        if('status'!==ev.type){
-            console.warn("Unexpected module-type message:",ev);
+    SF.addMsgHandler('working',function f(ev){
+      switch(ev.data){
+          case 'start': /* See notes in preStartWork(). */; return;
+          case 'end':
+            preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig;
+            btnShellExec.innerText = preStartWork._.btnLabel;
+            btnShellExec.removeAttribute('disabled');
+            btnInterrupt.setAttribute('disabled','disabled');
             return;
+      }
+      console.warn("Unhandled 'working' event:",ev.data);
+    });
+
+    /* For each checkbox 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'));
         }
-        if(!f.ui){
-            f.ui = {
-                status: E('#module-status'),
-                progress: E('#module-progress'),
-                spinner: E('#module-spinner')
-            };
-        }
-        const msg = ev.data;
-        if(f.ui.progres){
-            progress.value = msg.step;
-            progress.max = msg.step + 1/*we don't know how many steps to expect*/;
-        }
-        if(1==msg.step){
-            f.ui.progress.classList.remove('hidden');
-            f.ui.spinner.classList.remove('hidden');
-        }
-        if(msg.text){
-            f.ui.status.classList.remove('hidden');
-            f.ui.status.innerText = msg.text;
-        }else{
-            if(f.ui.progress){
-                f.ui.progress.remove();
-                f.ui.spinner.remove();
-                delete f.ui.progress;
-                delete f.ui.spinner;
-            }
-            f.ui.status.classList.add('hidden');
-            /* The module can post messages about fatal problems,
-               e.g. an exit() being triggered or assertion failure,
-               after the last "load" message has arrived, so
-               leave f.ui.status and message listener intact. */
-        }
+        e.addEventListener('change', function(){
+          SF.config[this.dataset.config] = this.checked;
+          SF.storeConfig();
+        }, false);
+      });
+    /* For each button with data-cmd=X, map a click handler which
+       calls SF.dbExec(X). */
+    const cmdClick = function(){SF.dbExec(this.dataset.cmd);};
+    EAll('button[data-cmd]').forEach(
+      e => e.addEventListener('click', cmdClick, false)
+    );
+
+    btnInterrupt.addEventListener('click',function(){
+      SF.wMsg('interrupt');
     });
 
+    /** Initiate a download of the db. */
+    const btnExport = E('#btn-export');
+    const eLoadDb = E('#load-db');
+    const btnLoadDb = E('#btn-load-db');
+    btnLoadDb.addEventListener('click', ()=>eLoadDb.click());
     /**
-       The 'fiddle-ready' event is fired (with no payload) when the
-       wasm module has finished loading. Interestingly, that happens
-       _before_ the final module:status event */
-    SF.addMsgHandler('fiddle-ready', function(){
-        SF.clearMsgHandlers('fiddle-ready');
-        self.onSFLoaded();
+       Enables (if passed true) or disables all UI elements which
+       "might," if timed "just right," interfere with an
+       in-progress db import/export/exec operation.
+    */
+    const enableMutatingElements = function f(enable){
+      if(!f._elems){
+        f._elems = [
+          /* UI elements to disable while import/export are
+             running. Normally the export is fast enough
+             that this won't matter, but we really don't
+             want to be reading (from outside of sqlite) the
+             db when the user taps btnShellExec. */
+          btnShellExec, btnExport, eLoadDb
+        ];
+      }
+      f._elems.forEach( enable
+                        ? (e)=>e.removeAttribute('disabled')
+                        : (e)=>e.setAttribute('disabled','disabled') );
+    };
+    btnExport.addEventListener('click',function(){
+      enableMutatingElements(false);
+      SF.wMsg('db-export');
+    });
+    SF.addMsgHandler('db-export', function(ev){
+      enableMutatingElements(true);
+      ev = ev.data;
+      if(ev.error){
+        SF.echo("Export failed:",ev.error);
+        return;
+      }
+      const blob = new Blob([ev.buffer], {type:"application/x-sqlite3"});
+      const a = document.createElement('a');
+      document.body.appendChild(a);
+      a.href = window.URL.createObjectURL(blob);
+      a.download = ev.filename;
+      a.addEventListener('click',function(){
+        setTimeout(function(){
+          SF.echo("Exported (possibly auto-downloaded):",ev.filename);
+          window.URL.revokeObjectURL(a.href);
+          a.remove();
+        },500);
+      });
+      a.click();
     });
-
     /**
-       Performs all app initialization which must wait until after the
-       worker module is loaded. This function removes itself when it's
-       called.
+       Handle load/import of an external db file.
     */
-    self.onSFLoaded = function(){
-        delete this.onSFLoaded;
-        // Unhide all elements which start out hidden
-        EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden'));
-        E('#btn-reset').addEventListener('click',()=>SF.resetDb());
-        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(ev){
-            let sql;
-            ev.preventDefault();
-            if(taInput.selectionStart<taInput.selectionEnd){
-                sql = taInput.value.substring(taInput.selectionStart,taInput.selectionEnd).trim();
-            }else{
-                sql = taInput.value.trim();
-            }
-            if(sql) SF.dbExec(sql);
-        },false);
-
-        const btnInterrupt = E("#btn-interrupt");
-        //btnInterrupt.classList.add('hidden');
-        /** To be called immediately before work is sent to the
-            worker. Updates some UI elements. The 'working'/'end'
-            event will apply the inverse, undoing the bits this
-            function does. This impl is not in the 'working'/'start'
-            event handler because that event is given to us
-            asynchronously _after_ we need to have performed this
-            work.
-        */
-        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');
-            btnInterrupt.removeAttribute('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){
-            switch(ev.data){
-                case 'start': /* See notes in preStartWork(). */; return;
-                case 'end':
-                    preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig;
-                    btnShellExec.innerText = preStartWork._.btnLabel;
-                    btnShellExec.removeAttribute('disabled');
-                    btnInterrupt.setAttribute('disabled','disabled');
-                    return;
-            }
-            console.warn("Unhandled 'working' event:",ev.data);
+    eLoadDb.addEventListener('change',function(){
+      const f = this.files[0];
+      const r = new FileReader();
+      const status = {loaded: 0, total: 0};
+      enableMutatingElements(false);
+      r.addEventListener('loadstart', function(){
+        SF.echo("Loading",f.name,"...");
+      });
+      r.addEventListener('progress', function(ev){
+        SF.echo("Loading progress:",ev.loaded,"of",ev.total,"bytes.");
+      });
+      const that = this;
+      r.addEventListener('load', function(){
+        enableMutatingElements(true);
+        SF.echo("Loaded",f.name+". Opening db...");
+        SF.wMsg('open',{
+          filename: f.name,
+          buffer: this.result
         });
+      });
+      r.addEventListener('error',function(){
+        enableMutatingElements(true);
+        SF.echo("Loading",f.name,"failed for unknown reasons.");
+      });
+      r.addEventListener('abort',function(){
+        enableMutatingElements(true);
+        SF.echo("Cancelled loading of",f.name+".");
+      });
+      r.readAsArrayBuffer(f);
+    });
 
-        /* For each checkbox 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;
-                    SF.storeConfig();
-                }, false);
-            });
-        /* For each button with data-cmd=X, map a click handler which
-           calls SF.dbExec(X). */
-        const cmdClick = function(){SF.dbExec(this.dataset.cmd);};
-        EAll('button[data-cmd]').forEach(
-            e => e.addEventListener('click', cmdClick, false)
-        );
-
-        btnInterrupt.addEventListener('click',function(){
-            SF.wMsg('interrupt');
-        });
+    EAll('fieldset.collapsible').forEach(function(fs){
+      const btnToggle = E(fs,'legend > .fieldset-toggle'),
+            content = EAll(fs,':scope > div');
+      btnToggle.addEventListener('click', function(){
+        fs.classList.toggle('collapsed');
+        content.forEach((d)=>d.classList.toggle('hidden'));
+      }, false);
+    });
 
-        /** Initiate a download of the db. */
-        const btnExport = E('#btn-export');
-        const eLoadDb = E('#load-db');
-        const btnLoadDb = E('#btn-load-db');
-        btnLoadDb.addEventListener('click', ()=>eLoadDb.click());
-        /**
-           Enables (if passed true) or disables all UI elements which
-           "might," if timed "just right," interfere with an
-           in-progress db import/export/exec operation.
-        */
-        const enableMutatingElements = function f(enable){
-            if(!f._elems){
-                f._elems = [
-                    /* UI elements to disable while import/export are
-                       running. Normally the export is fast enough
-                       that this won't matter, but we really don't
-                       want to be reading (from outside of sqlite) the
-                       db when the user taps btnShellExec. */
-                    btnShellExec, btnExport, eLoadDb
-                ];
-            }
-            f._elems.forEach( enable
-                              ? (e)=>e.removeAttribute('disabled')
-                              : (e)=>e.setAttribute('disabled','disabled') );
+    /**
+       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;
         };
-        btnExport.addEventListener('click',function(){
-            enableMutatingElements(false);
-            SF.wMsg('db-export');
-        });
-        SF.addMsgHandler('db-export', function(ev){
-            enableMutatingElements(true);
-            ev = ev.data;
-            if(ev.error){
-                SF.echo("Export failed:",ev.error);
-                return;
-            }
-            const blob = new Blob([ev.buffer], {type:"application/x-sqlite3"});
-            const a = document.createElement('a');
-            document.body.appendChild(a);
-            a.href = window.URL.createObjectURL(blob);
-            a.download = ev.filename;
-            a.addEventListener('click',function(){
-                setTimeout(function(){
-                    SF.echo("Exported (possibly auto-downloaded):",ev.filename);
-                    window.URL.revokeObjectURL(a.href);
-                    a.remove();
-                },500);
-            });
-            a.click();
-        });
-        /**
-           Handle load/import of an external db file.
-        */
-        eLoadDb.addEventListener('change',function(){
-            const f = this.files[0];
-            const r = new FileReader();
-            const status = {loaded: 0, total: 0};
-            enableMutatingElements(false);
-            r.addEventListener('loadstart', function(){
-                SF.echo("Loading",f.name,"...");
-            });
-            r.addEventListener('progress', function(ev){
-                SF.echo("Loading progress:",ev.loaded,"of",ev.total,"bytes.");
-            });
-            const that = this;
-            r.addEventListener('load', function(){
-                enableMutatingElements(true);
-                SF.echo("Loaded",f.name+". Opening db...");
-                SF.wMsg('open',{
-                    filename: f.name,
-                    buffer: this.result
-                });
-            });
-            r.addEventListener('error',function(){
-                enableMutatingElements(true);
-                SF.echo("Loading",f.name,"failed for unknown reasons.");
-            });
-            r.addEventListener('abort',function(){
-                enableMutatingElements(true);
-                SF.echo("Cancelled loading of",f.name+".");
-            });
-            r.readAsArrayBuffer(f);
-        });
+      }
+      f.extra = 0;
+      f.measure(e,0);
+      return f.extra;
+    };
 
-        EAll('fieldset.collapsible').forEach(function(fs){
-            const btnToggle = E(fs,'legend > .fieldset-toggle'),
-                  content = EAll(fs,':scope > div');
-            btnToggle.addEventListener('click', function(){
-                fs.classList.toggle('collapsed');
-                content.forEach((d)=>d.classList.toggle('hidden'));
-            }, false);
-        });
+    /**
+       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.
 
-        /**
-           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;
-        };
+       If passed only 1 argument, or passed a falsy 2nd argument,
+       the default wait time set in this function's $defaultDelay
+       property is used.
 
-        /**
-           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);
-            };
+       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);
         };
-        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 main view
-               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 appViews = EAll('.app-view');
-            const elemsToCount = [
-                /* Elements which we need to always count in the
-                   visible body size. */
-                E('body > header'),
-                E('body > footer')
-            ];
-            const resized = function f(){
-                if(f.$disabled) return;
-                const wh = window.innerHeight;
-                var ht;
-                var extra = 0;
-                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: "Help", sql:
+        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 main view
+         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 appViews = EAll('.app-view');
+      const elemsToCount = [
+        /* Elements which we need to always count in the
+           visible body size. */
+        E('body > header'),
+        E('body > footer')
+      ];
+      const resized = function f(){
+        if(f.$disabled) return;
+        const wh = window.innerHeight;
+        var ht;
+        var extra = 0;
+        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: "Help", sql:
 `-- ================================================
 -- Use ctrl-enter or shift-enter to execute sqlite3
 -- shell commands and SQL.
@@ -756,55 +755,53 @@ SELECT * FROM t;`},
     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;
-                SF.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(SF.dbExec.bind(SF),{
-                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"));
-            head.appendChild(btnToggleView);
-            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*/;
+      ];
+      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;
+        SF.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(SF.dbExec.bind(SF),{
+        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"));
+      head.appendChild(btnToggleView);
+      btnToggleView.addEventListener('click',function f(){
+        EAll('.app-view').forEach(e=>e.classList.toggle('hidden'));
+        if(document.body.classList.toggle('terminal-mode')){
+          ForceResizeKludge();
         }
-        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();
-
-        btnShellExec.click();
-    }/*onSFLoaded()*/;
+      }, false);
+      btnToggleView.click()/*default to terminal view*/;
+    }
+    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');
+    SF.dbExec(null);
+    delete ForceResizeKludge.$disabled;
+    ForceResizeKludge();
+  }/*onSFLoaded()*/;
 })();
index e575dac86d80fd7a063df3955d8e3760d6983cdc..a911a21d4fe57857f452540853b0c190476271f4 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Reformulate\ssome\sJS\sto\swork\saround\sa\sbuggy/broken\scode\stransformation\sin\sone\sof\sthe\sEmscripten-driven\scode\soptimizers.
-D 2022-09-21T20:24:12.588
+C Refactoring\stowards\sgetting\sfiddle\sto\ssupport\sOPFS\sas\sa\sfirst-class\scitizen.\sCertain\soperations,\se.g.\simport,\sexport,\sand\sunlink,\sare\snot\sOPFS-aware.
+D 2022-09-24T07:36:45.332
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -472,7 +472,7 @@ F ext/session/test_session.c f433f68a8a8c64b0f5bc74dc725078f12483301ad4ae8375205
 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
 F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb
-F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 7fb73f7150ab79d83bb45a67d257553c905c78cd3d693101699243f36c5ae6c3
+F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 105f6f7f211f49dea8fa6ee8b7b56492d5f9237ab0d1c1b3c970df11e4d0df02
 F ext/wasm/EXPORTED_RUNTIME_METHODS.fiddle 0e88c8cfc3719e4b7e74980d9da664c709e68acf863e48386cda376edfd3bfb0
 F ext/wasm/GNUmakefile 34a84e30e6b25e24959a8264e9dec020dffa82d96879dc55ad65d3c31c95d3b1
 F ext/wasm/README.md e1ee1e7c321c6a250bf78a84ca6f5882890a237a450ba5a0649c7a8399194c52
@@ -485,7 +485,7 @@ F ext/wasm/api/sqlite3-api-cleanup.js 8564a6077cdcaea9a9f428a019af8a05887f0131e6
 F ext/wasm/api/sqlite3-api-glue.js cfff894bdf98a6c579975d09dd45471b0e3399f08a6f9e44a22646e8403196ed
 F ext/wasm/api/sqlite3-api-oo1.js f974e79d9af8f26bf33928c5730b0988cc706d14f59a5fe36394739b92249841
 F ext/wasm/api/sqlite3-api-opfs.js d623ea3519cd81fe18e243adfdd07cd1fa4b07ff3b0fd0d2b269beb0e127acb3
-F ext/wasm/api/sqlite3-api-prologue.js 76fcd56005717cf4ef6c57cee4d7679c9a5fa8c060110485c1318f5b548abac8
+F ext/wasm/api/sqlite3-api-prologue.js a50ba8618e81a10a4fecd70f8723a7295cfcc0babd6df1dd018e7c5db2904aac
 F ext/wasm/api/sqlite3-api-worker1.js 2eeb2a24e1a90322d84a9b88a99919b806623de62792436446099c0988f2030b
 F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
 F ext/wasm/api/sqlite3-wasm.c d1c0724136480a459d9dda4b76a665691a172d5cba96729d26d26acf6480bc9b
@@ -494,7 +494,7 @@ F ext/wasm/batch-runner.js 6f5b86e0b5519a9a941d9f17ee9c5ecdc63f452f157602fe7fdf8
 F ext/wasm/common/SqliteTestUtil.js 529161a624265ba84271a52db58da022649832fa1c71309fb1e02cc037327a2b
 F ext/wasm/common/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
 F ext/wasm/common/testing.css 3a5143699c2b73a85b962271e1a9b3241b30d90e30d895e4f55665e648572962
-F ext/wasm/common/whwasmutil.js f64f94e2a3e1d8fd2139b4d80f00a3ab504985e38103c45b1ff15ec8b44fcce3
+F ext/wasm/common/whwasmutil.js ba863cb5b837654736a6e20636c887016f08639127c08d9d985db474d1cec1a4
 F ext/wasm/demo-123-worker.html e419b66495d209b5211ec64903b4cfb3ca7df20d652b41fcd28bf018a773234f
 F ext/wasm/demo-123.html aa281d33b7eefa755f3122b7b5a18f39a42dc5fb69c8879171bf14b4c37c4ec4
 F ext/wasm/demo-123.js 234655683e35a4543a23de7b10800d76b0369947b33e089e5613171fa7795afb
@@ -502,9 +502,9 @@ F ext/wasm/demo-kvvfs1.html 7d4f28873de67f51ac18c584b7d920825139866a96049a49c424
 F ext/wasm/demo-kvvfs1.js e884ea35022d772c0d1dd884b40011413696438394f605c6cd4808cfb1642a4a
 F ext/wasm/fiddle.make fd56fa21bada6ecbf860686a9a789ebda7cc3d9b60835927000fcb00246ea50f
 F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
-F ext/wasm/fiddle/fiddle-worker.js 3a258f7c79f36958d8f63bceb3dd178bb2dfc6a802329e9349612ecb1d9fcd09
+F ext/wasm/fiddle/fiddle-worker.js b6aea063c591c672cc575ef726d41d2b486e16bf7623a9775a374fd27b29a133
 F ext/wasm/fiddle/fiddle.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2
-F ext/wasm/fiddle/fiddle.js dae246414ebd56ecdc09cf95c43b2d50e1332a331832a27915782c7767f92f45
+F ext/wasm/fiddle/fiddle.js e7c6dee946818d0e6a10c89b640440fd5d93cbb9bddea490b98cf54e8bb67ae6
 F ext/wasm/index.html 8b4b7ea052d558262c8466f94326fb455c21049b2d1d3577ed0a5fce15101ba8
 F ext/wasm/jaccwabyt/jaccwabyt.js 0d7f32817456a0f3937fcfd934afeb32154ca33580ab264dab6c285e6dbbd215
 F ext/wasm/jaccwabyt/jaccwabyt.md 447cc02b598f7792edaa8ae6853a7847b8178a18ed356afacbdbf312b2588106
@@ -615,7 +615,7 @@ F src/random.c 546d6feb15ec69c1aafe9bb351a277cbb498fd5410e646add673acb805714960
 F src/resolve.c efea4e5fbecfd6d0a9071b0be0d952620991673391b6ffaaf4c277b0bb674633
 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
 F src/select.c d69dfb5b082f9a25e6700e152ddb3d942359b847b1df504eb09f9b4531844f8d
-F src/shell.c.in fd57bd685265eda436f98b06adca63c6a6cc170e12c7cd9b0c44826ea65cc6be
+F src/shell.c.in c4625fa9493cf815ff15e62bee5af93e91bbdb1a0c4e2c5a34a3744df3d5daaf
 F src/sqlite.h.in b9b7fd73239d94db20332bb6e504688001e5564b655e1318a4427a1caef4b99e
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h a988810c9b21c0dc36dc7a62735012339dc76fc7ab448fb0792721d30eacb69d
@@ -2026,8 +2026,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 7c7fb7535e86b3960eea7f29ab7e6d5197c166b4ee64ad4a9bc0749f2869badc
-R 21b4f88ebebc444a2dca81501f78488d
+P e1249369d5ec1c582c280b1f578b35d53637fdf1cbd97c16d5ed95b136b83e56
+R bfb613620806c41d29eb36b1d39427ef
 U stephan
-Z de96dd791c0a9ecdd14104957785e773
+Z 135731a0096fbab4e7ac0c6ece9bf8ed
 # Remove this line to create a well-formed Fossil manifest.
index 28b4a319ca61165d50a05618c32d2988b46ab0de..ea285c7d79f29f7bbde4ea988b189bbad67fed9e 100644 (file)
@@ -1 +1 @@
-e1249369d5ec1c582c280b1f578b35d53637fdf1cbd97c16d5ed95b136b83e56
\ No newline at end of file
+1b923ed6438d7fef4508936e0c4bc026a368721698b1539961e3fb3140a185cb
\ No newline at end of file
index bef30f5dfc311c95d7f8f45ca6ce43ef49aa6643..bafcc697f2e2ed3c32dfcd421241a203b71bff5d 100644 (file)
@@ -1172,6 +1172,7 @@ struct ShellState {
   struct {
     const char * zInput; /* Input string from wasm/JS proxy */
     const char * zPos;   /* Cursor pos into zInput */
+    const char * zDefaultDbName; /* Default name for db file */
   } wasm;
 #endif
 };
@@ -12057,6 +12058,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
 #ifdef SQLITE_SHELL_FIDDLE
   stdin_is_interactive = 0;
   stdout_is_console = 1;
+  data.wasm.zDefaultDbName = "/fiddle.sqlite3";
 #else
   stdin_is_interactive = isatty(0);
   stdout_is_console = isatty(1);
@@ -12686,7 +12688,7 @@ const char * fiddle_db_filename(const char * zDbName){
 void fiddle_reset_db(void){
   char *zFilename = 0;
   if(0==globalDb){
-    shellState.pAuxDb->zDbFilename = "/fiddle.sqlite3";
+    shellState.pAuxDb->zDbFilename = shellState.wasm.zDefaultDbName;
   }else{
     zFilename =
       sqlite3_mprintf("%s", sqlite3_db_filename(globalDb, "main"));
@@ -12701,48 +12703,19 @@ void fiddle_reset_db(void){
 }
 
 /*
-** Trivial exportable function for emscripten. Needs to be exported using:
-**
-** emcc ..flags... -sEXPORTED_FUNCTIONS=_fiddle_exec -sEXPORTED_RUNTIME_METHODS=ccall,cwrap
-**
-** (Note the underscore before the function name.) It processes zSql
-** as if it were input to the sqlite3 shell and redirects all output
-** to the wasm binding.
+** Trivial exportable function for emscripten. It processes zSql as if
+** it were input to the sqlite3 shell and redirects all output to the
+** wasm binding. If fiddle_main() has not been called by the time this
+** is called, this function calls it with a conservative set of
+** flags.
 */
 void fiddle_exec(const char * zSql){
-  static int once = 0;
-  int rc = 0;
-  if(!once){
-    /* Simulate an argv array for main() */
-    static char * argv[] = {"fiddle",
-                            "-bail",
-                            "-safe"};
-    rc = fiddle_main((int)(sizeof(argv)/sizeof(argv[0])), argv);
-    once = rc ? -1 : 1;
-    memset(&shellState.wasm, 0, sizeof(shellState.wasm));
-    printf(
-        "SQLite version %s %.19s\n" /*extra-version-info*/,
-        sqlite3_libversion(), sqlite3_sourceid()
-    );
-    puts("WASM shell");
-    puts("Enter \".help\" for usage hints.");
-    if(once>0){
-      fiddle_reset_db();
-    }
-    if(shellState.db){
-      printf("Connected to %s.\n", fiddle_db_filename(NULL));
-    }else{
-      fprintf(stderr,"ERROR initializing db!\n");
-      return;
-    }
-  }
-  if(once<0){
-    puts("DB init failed. Not executing SQL.");
-  }else if(zSql && *zSql){
+  if(zSql && *zSql){
+    if('.'==*zSql) puts(zSql);
     shellState.wasm.zInput = zSql;
     shellState.wasm.zPos = zSql;
     process_input(&shellState);
-    memset(&shellState.wasm, 0, sizeof(shellState.wasm));
+    shellState.wasm.zInput = shellState.wasm.zPos = 0;
   }
 }
 #endif /* SQLITE_SHELL_FIDDLE */