You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by wo...@apache.org on 2020/04/15 00:29:01 UTC

[couchdb] branch feat/spidermonkey-68 created (now cb87298)

This is an automated email from the ASF dual-hosted git repository.

wohali pushed a change to branch feat/spidermonkey-68
in repository https://gitbox.apache.org/repos/asf/couchdb.git.


      at cb87298  First pass at SpiderMonkey 68 support

This branch includes the following new commits:

     new cb87298  First pass at SpiderMonkey 68 support

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[couchdb] 01/01: First pass at SpiderMonkey 68 support

Posted by wo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

wohali pushed a commit to branch feat/spidermonkey-68
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit cb87298eb897453e4658fd8ccbd5cfd0dc33c14e
Author: Joan Touzet <wo...@apache.org>
AuthorDate: Wed Apr 15 00:28:27 2020 +0000

    First pass at SpiderMonkey 68 support
---
 .gitignore                          |   1 +
 rebar.config.script                 |  16 +-
 src/couch/priv/couch_js/68/help.h   |  86 +++++
 src/couch/priv/couch_js/68/http.cpp | 711 ++++++++++++++++++++++++++++++++++++
 src/couch/priv/couch_js/68/http.h   |  27 ++
 src/couch/priv/couch_js/68/main.cpp | 499 +++++++++++++++++++++++++
 src/couch/priv/couch_js/68/utf8.cpp | 310 ++++++++++++++++
 src/couch/priv/couch_js/68/utf8.h   |  19 +
 src/couch/priv/couch_js/68/util.cpp | 351 ++++++++++++++++++
 src/couch/priv/couch_js/68/util.h   |  60 +++
 src/couch/rebar.config.script       |  66 ++--
 support/build_js.escript            |   6 +
 12 files changed, 2115 insertions(+), 37 deletions(-)

diff --git a/.gitignore b/.gitignore
index 60e6d14..551a6a6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@
 .rebar/
 .eunit/
 cover/
+core
 log
 apache-couchdb-*/
 bin/
diff --git a/rebar.config.script b/rebar.config.script
index 408ad3d..ddf14bf 100644
--- a/rebar.config.script
+++ b/rebar.config.script
@@ -139,7 +139,7 @@ SubDirs = [
     "src/setup",
     "src/smoosh",
     "rel"
-],
+].
 
 DepDescs = [
 %% Independent Apps
@@ -162,18 +162,18 @@ DepDescs = [
 {mochiweb,         "mochiweb",         {tag, "v2.20.0"}},
 {meck,             "meck",             {tag, "0.8.8"}},
 {recon,            "recon",            {tag, "2.5.0"}}
-],
+].
 
-WithProper = lists:keyfind(with_proper, 1, CouchConfig) == {with_proper, true},
+WithProper = lists:keyfind(with_proper, 1, CouchConfig) == {with_proper, true}.
 
 OptionalDeps = case WithProper of
     true ->
         [{proper, {url, "https://github.com/proper-testing/proper"}, {tag, "v1.3"}}];
     false ->
         []
-end,
+end.
 
-BaseUrl = "https://github.com/apache/",
+BaseUrl = "https://github.com/apache/".
 
 MakeDep = fun
     ({AppName, {url, Url}, Version}) ->
@@ -186,12 +186,12 @@ MakeDep = fun
     ({AppName, RepoName, Version, Options}) ->
         Url = BaseUrl ++ "couchdb-" ++ RepoName ++ ".git",
         {AppName, ".*", {git, Url, Version}, Options}
-end,
+end.
 
 ErlOpts = case os:getenv("ERL_OPTS") of
     false -> [];
     Opts -> [list_to_atom(O) || O <- string:tokens(Opts, ",")]
-end,
+end.
 
 AddConfig = [
     {require_otp_vsn, "19|20|21|22"},
@@ -210,7 +210,7 @@ AddConfig = [
             sasl, setup, ssl, stdlib, syntax_tools, xmerl]},
         {warnings, [unmatched_returns, error_handling, race_conditions]}]},
     {post_hooks, [{compile, "escript support/build_js.escript"}]}
-],
+].
 
 C = lists:foldl(fun({K, V}, CfgAcc) ->
     lists:keystore(K, 1, CfgAcc, {K, V})
diff --git a/src/couch/priv/couch_js/68/help.h b/src/couch/priv/couch_js/68/help.h
new file mode 100644
index 0000000..678651f
--- /dev/null
+++ b/src/couch/priv/couch_js/68/help.h
@@ -0,0 +1,86 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef COUCHJS_HELP_H
+#define COUCHJS_HELP_H
+
+#include "config.h"
+
+static const char VERSION_TEMPLATE[] =
+    "%s - %s\n"
+    "\n"
+    "Licensed under the Apache License, Version 2.0 (the \"License\"); you may "
+        "not use\n"
+    "this file except in compliance with the License. You may obtain a copy of"
+        "the\n"
+    "License at\n"
+    "\n"
+    "  http://www.apache.org/licenses/LICENSE-2.0\n"
+    "\n"
+    "Unless required by applicable law or agreed to in writing, software "
+        "distributed\n"
+    "under the License is distributed on an \"AS IS\" BASIS, WITHOUT "
+        "WARRANTIES OR\n"
+    "CONDITIONS OF ANY KIND, either express or implied. See the License "
+        "for the\n"
+    "specific language governing permissions and limitations under the "
+        "License.\n";
+
+static const char USAGE_TEMPLATE[] =
+    "Usage: %s [FILE]\n"
+    "\n"
+    "The %s command runs the %s JavaScript interpreter.\n"
+    "\n"
+    "The exit status is 0 for success or 1 for failure.\n"
+    "\n"
+    "Options:\n"
+    "\n"
+    "  -h          display a short help message and exit\n"
+    "  -V          display version information and exit\n"
+    "  -H          enable %s cURL bindings (only avaiable\n"
+    "              if package was built with cURL available)\n"
+    "  -T          enable test suite specific functions (these\n"
+    "              should not be enabled for production systems)\n"
+    "  -S SIZE     specify that the runtime should allow at\n"
+    "              most SIZE bytes of memory to be allocated\n"
+    "              default is 64 MiB\n"
+    "  -u FILE     path to a .uri file containing the address\n"
+    "              (or addresses) of one or more servers\n"
+    "  --eval      Enable runtime code evaluation (dangerous!)\n"
+    "\n"
+    "Report bugs at <%s>.\n";
+
+#define BASENAME COUCHJS_NAME
+
+#define couch_version(basename)  \
+    fprintf(                     \
+            stdout,              \
+            VERSION_TEMPLATE,    \
+            basename,            \
+            PACKAGE_STRING)
+
+#define DISPLAY_VERSION couch_version(BASENAME)
+
+
+#define couch_usage(basename) \
+    fprintf(                                    \
+            stdout,                             \
+            USAGE_TEMPLATE,                     \
+            basename,                           \
+            basename,                           \
+            PACKAGE_NAME,                       \
+            basename,                           \
+            PACKAGE_BUGREPORT)
+
+#define DISPLAY_USAGE couch_usage(BASENAME)
+
+#endif // Included help.h
diff --git a/src/couch/priv/couch_js/68/http.cpp b/src/couch/priv/couch_js/68/http.cpp
new file mode 100644
index 0000000..885bb56
--- /dev/null
+++ b/src/couch/priv/couch_js/68/http.cpp
@@ -0,0 +1,711 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <jsapi.h>
+#include <js/Initialization.h>
+#include <js/MemoryFunctions.h>
+#include "config.h"
+#include "utf8.h"
+#include "util.h"
+
+// Soft dependency on cURL bindings because they're
+// only used when running the JS tests from the
+// command line which is rare.
+#ifndef HAVE_CURL
+
+void
+http_check_enabled()
+{
+    fprintf(stderr, "HTTP API was disabled at compile time.\n");
+    exit(3);
+}
+
+
+bool
+http_ctor(JSContext* cx, JSObject* req)
+{
+    return false;
+}
+
+
+void
+http_dtor(JSFreeOp* fop, JSObject* req)
+{
+    return;
+}
+
+
+bool
+http_open(JSContext* cx, JSObject* req, JS::Value mth, JS::Value url, JS::Value snc)
+{
+    return false;
+}
+
+
+bool
+http_set_hdr(JSContext* cx, JSObject* req, JS::Value name, JS::Value val)
+{
+    return false;
+}
+
+
+bool
+http_send(JSContext* cx, JSObject* req, JS::Value body)
+{
+    return false;
+}
+
+
+int
+http_status(JSContext* cx, JSObject* req)
+{
+    return -1;
+}
+
+bool
+http_uri(JSContext* cx, JSObject* req, couch_args* args, JS::Value* uri_val)
+{
+    return false;
+}
+
+
+#else
+#include <curl/curl.h>
+#ifndef XP_WIN
+#include <unistd.h>
+#endif
+
+
+void
+http_check_enabled()
+{
+    return;
+}
+
+
+// Map some of the string function names to things which exist on Windows
+#ifdef XP_WIN
+#define strcasecmp _strcmpi
+#define strncasecmp _strnicmp
+#define snprintf _snprintf
+#endif
+
+
+typedef struct curl_slist CurlHeaders;
+
+
+typedef struct {
+    int             method;
+    char*           url;
+    CurlHeaders*    req_headers;
+    int16_t          last_status;
+} HTTPData;
+
+
+const char* METHODS[] = {"GET", "HEAD", "POST", "PUT", "DELETE", "COPY", "OPTIONS", NULL};
+
+
+#define GET     0
+#define HEAD    1
+#define POST    2
+#define PUT     3
+#define DELETE  4
+#define COPY    5
+#define OPTIONS 6
+
+
+static bool
+go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t blen);
+
+
+/*static JSString*
+str_from_binary(JSContext* cx, char* data, size_t length);
+*/
+
+
+bool
+http_ctor(JSContext* cx, JSObject* req)
+{
+    HTTPData* http = NULL;
+    bool ret = false;
+
+    http = (HTTPData*) malloc(sizeof(HTTPData));
+    if(!http)
+    {
+        JS_ReportErrorUTF8(cx, "Failed to create CouchHTTP instance.");
+        goto error;
+    }
+
+    http->method = -1;
+    http->url = NULL;
+    http->req_headers = NULL;
+    http->last_status = -1;
+
+    JS_SetPrivate(req, http);
+
+    ret = true;
+    goto success;
+
+error:
+    if(http) free(http);
+
+success:
+    return ret;
+}
+
+
+void
+http_dtor(JSFreeOp* fop, JSObject* obj)
+{
+    HTTPData* http = (HTTPData*) JS_GetPrivate(obj);
+    if(http) {
+        if(http->url) free(http->url);
+        if(http->req_headers) curl_slist_free_all(http->req_headers);
+        free(http);
+    }
+}
+
+
+bool
+http_open(JSContext* cx, JSObject* req, JS::Value mth, JS::Value url, JS::Value snc)
+{
+    HTTPData* http = (HTTPData*) JS_GetPrivate(req);
+    char* method = NULL;
+    int methid;
+    bool ret = false;
+
+    if(!http) {
+        JS_ReportErrorUTF8(cx, "Invalid CouchHTTP instance.");
+        goto done;
+    }
+
+    if(mth.isUndefined()) {
+        JS_ReportErrorUTF8(cx, "You must specify a method.");
+        goto done;
+    }
+
+    method = enc_string(cx, mth, NULL);
+    if(!method) {
+        JS_ReportErrorUTF8(cx, "Failed to encode method.");
+        goto done;
+    }
+
+    for(methid = 0; METHODS[methid] != NULL; methid++) {
+        if(strcasecmp(METHODS[methid], method) == 0) break;
+    }
+
+    if(methid > OPTIONS) {
+        JS_ReportErrorUTF8(cx, "Invalid method specified.");
+        goto done;
+    }
+
+    http->method = methid;
+
+    if(url.isUndefined()) {
+        JS_ReportErrorUTF8(cx, "You must specify a URL.");
+        goto done;
+    }
+
+    if(http->url != NULL) {
+        free(http->url);
+        http->url = NULL;
+    }
+
+    http->url = enc_string(cx, url, NULL);
+    if(http->url == NULL) {
+        JS_ReportErrorUTF8(cx, "Failed to encode URL.");
+        goto done;
+    }
+
+    if(snc.isBoolean() && snc.isTrue()) {
+        JS_ReportErrorUTF8(cx, "Synchronous flag must be false.");
+        goto done;
+    }
+
+    if(http->req_headers) {
+        curl_slist_free_all(http->req_headers);
+        http->req_headers = NULL;
+    }
+
+    // Disable Expect: 100-continue
+    http->req_headers = curl_slist_append(http->req_headers, "Expect:");
+
+    ret = true;
+
+done:
+    if(method) free(method);
+    return ret;
+}
+
+
+bool
+http_set_hdr(JSContext* cx, JSObject* req, JS::Value name, JS::Value val)
+{
+    HTTPData* http = (HTTPData*) JS_GetPrivate(req);
+    char* keystr = NULL;
+    char* valstr = NULL;
+    char* hdrbuf = NULL;
+    size_t hdrlen = -1;
+    bool ret = false;
+
+    if(!http) {
+        JS_ReportErrorUTF8(cx, "Invalid CouchHTTP instance.");
+        goto done;
+    }
+
+    if(name.isUndefined())
+    {
+        JS_ReportErrorUTF8(cx, "You must speciy a header name.");
+        goto done;
+    }
+
+    keystr = enc_string(cx, name, NULL);
+    if(!keystr)
+    {
+        JS_ReportErrorUTF8(cx, "Failed to encode header name.");
+        goto done;
+    }
+
+    if(val.isUndefined())
+    {
+        JS_ReportErrorUTF8(cx, "You must specify a header value.");
+        goto done;
+    }
+
+    valstr = enc_string(cx, val, NULL);
+    if(!valstr)
+    {
+        JS_ReportErrorUTF8(cx, "Failed to encode header value.");
+        goto done;
+    }
+
+    hdrlen = strlen(keystr) + strlen(valstr) + 3;
+    hdrbuf = (char*) malloc(hdrlen * sizeof(char));
+    if(!hdrbuf) {
+        JS_ReportErrorUTF8(cx, "Failed to allocate header buffer.");
+        goto done;
+    }
+
+    snprintf(hdrbuf, hdrlen, "%s: %s", keystr, valstr);
+    http->req_headers = curl_slist_append(http->req_headers, hdrbuf);
+
+    ret = true;
+
+done:
+    if(keystr) free(keystr);
+    if(valstr) free(valstr);
+    if(hdrbuf) free(hdrbuf);
+    return ret;
+}
+
+bool
+http_send(JSContext* cx, JSObject* req, JS::Value body)
+{
+    HTTPData* http = (HTTPData*) JS_GetPrivate(req);
+    char* bodystr = NULL;
+    size_t bodylen = 0;
+    bool ret = false;
+
+    if(!http) {
+        JS_ReportErrorUTF8(cx, "Invalid CouchHTTP instance.");
+        goto done;
+    }
+
+    if(!body.isUndefined()) {
+        bodystr = enc_string(cx, body, &bodylen);
+        if(!bodystr) {
+            JS_ReportErrorUTF8(cx, "Failed to encode body.");
+            goto done;
+        }
+    }
+
+    ret = go(cx, req, http, bodystr, bodylen);
+
+done:
+    if(bodystr) free(bodystr);
+    return ret;
+}
+
+int
+http_status(JSContext* cx, JSObject* req)
+{
+    HTTPData* http = (HTTPData*) JS_GetPrivate(req);
+
+    if(!http) {
+        JS_ReportErrorUTF8(cx, "Invalid CouchHTTP instance.");
+        return false;
+    }
+
+    return http->last_status;
+}
+
+bool
+http_uri(JSContext* cx, JSObject* req, couch_args* args, JS::Value* uri_val)
+{
+    FILE* uri_fp = NULL;
+    JSString* uri_str;
+
+    // Default is http://localhost:15986/ when no uri file is specified
+    if (!args->uri_file) {
+        uri_str = JS_NewStringCopyZ(cx, "http://localhost:15986/");
+        *uri_val = JS::StringValue(uri_str);
+        JS_SetReservedSlot(req, 0, *uri_val);
+        return true;
+    }
+
+    // Else check to see if the base url is cached in a reserved slot
+    *uri_val = JS_GetReservedSlot(req, 0);
+    if (!(*uri_val).isUndefined()) {
+        return true;
+    }
+
+    // Read the first line of the couch.uri file.
+    if(!((uri_fp = fopen(args->uri_file, "r")) &&
+         (uri_str = couch_readline(cx, uri_fp)))) {
+        JS_ReportErrorUTF8(cx, "Failed to read couch.uri file.");
+        goto error;
+    }
+
+    fclose(uri_fp);
+    *uri_val = JS::StringValue(uri_str);
+    JS_SetReservedSlot(req, 0, *uri_val);
+    return true;
+
+error:
+    if(uri_fp) fclose(uri_fp);
+    return false;
+}
+
+
+// Curl Helpers
+
+typedef struct {
+    HTTPData*   http;
+    JSContext*  cx;
+    JSObject*   resp_headers;
+    char*       sendbuf;
+    size_t      sendlen;
+    size_t      sent;
+    int         sent_once;
+    char*       recvbuf;
+    size_t      recvlen;
+    size_t      read;
+} CurlState;
+
+/*
+ * I really hate doing this but this doesn't have to be
+ * uber awesome, it just has to work.
+ */
+CURL*       HTTP_HANDLE = NULL;
+char        ERRBUF[CURL_ERROR_SIZE];
+
+static size_t send_body(void *ptr, size_t size, size_t nmem, void *data);
+static int seek_body(void *ptr, curl_off_t offset, int origin);
+static size_t recv_body(void *ptr, size_t size, size_t nmem, void *data);
+static size_t recv_header(void *ptr, size_t size, size_t nmem, void *data);
+
+static bool
+go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
+{
+    CurlState state;
+    char* referer;
+    JSString* jsbody;
+    bool ret = false;
+    JS::Value tmp;
+    JS::RootedObject robj(cx, obj);
+    JS::RootedValue vobj(cx);
+
+
+    state.cx = cx;
+    state.http = http;
+
+    state.sendbuf = body;
+    state.sendlen = bodylen;
+    state.sent = 0;
+    state.sent_once = 0;
+
+    state.recvbuf = NULL;
+    state.recvlen = 0;
+    state.read = 0;
+
+    if(HTTP_HANDLE == NULL) {
+        HTTP_HANDLE = curl_easy_init();
+        curl_easy_setopt(HTTP_HANDLE, CURLOPT_READFUNCTION, send_body);
+        curl_easy_setopt(HTTP_HANDLE, CURLOPT_SEEKFUNCTION,
+                                        (curl_seek_callback) seek_body);
+        curl_easy_setopt(HTTP_HANDLE, CURLOPT_HEADERFUNCTION, recv_header);
+        curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEFUNCTION, recv_body);
+        curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOPROGRESS, 1);
+        curl_easy_setopt(HTTP_HANDLE, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
+        curl_easy_setopt(HTTP_HANDLE, CURLOPT_ERRORBUFFER, ERRBUF);
+        curl_easy_setopt(HTTP_HANDLE, CURLOPT_COOKIEFILE, "");
+        curl_easy_setopt(HTTP_HANDLE, CURLOPT_USERAGENT,
+                                            "CouchHTTP Client - Relax");
+    }
+
+    if(!HTTP_HANDLE) {
+        JS_ReportErrorUTF8(cx, "Failed to initialize cURL handle.");
+        if(state.recvbuf) JS_free(cx, state.recvbuf);
+        return ret;
+    }
+
+    tmp = JS_GetReservedSlot(obj, 0);
+
+    if(!(referer = enc_string(cx, tmp, NULL))) {
+        JS_ReportErrorUTF8(cx, "Failed to encode referer.");
+        if(state.recvbuf) JS_free(cx, state.recvbuf);
+          return ret;
+    }
+    curl_easy_setopt(HTTP_HANDLE, CURLOPT_REFERER, referer);
+    free(referer);
+
+    if(http->method < 0 || http->method > OPTIONS) {
+        JS_ReportErrorUTF8(cx, "INTERNAL: Unknown method.");
+        if(state.recvbuf) JS_free(cx, state.recvbuf);
+          return ret;
+    }
+
+    curl_easy_setopt(HTTP_HANDLE, CURLOPT_CUSTOMREQUEST, METHODS[http->method]);
+    curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOBODY, 0);
+    curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 1);
+    curl_easy_setopt(HTTP_HANDLE, CURLOPT_UPLOAD, 0);
+
+    if(http->method == HEAD) {
+        curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOBODY, 1);
+        curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 0);
+    } else if(http->method == POST || http->method == PUT) {
+        curl_easy_setopt(HTTP_HANDLE, CURLOPT_UPLOAD, 1);
+        curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 0);
+    }
+
+    if(body && bodylen) {
+        curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, bodylen);
+    } else {
+        curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, 0);
+    }
+
+    // curl_easy_setopt(HTTP_HANDLE, CURLOPT_VERBOSE, 1);
+
+    curl_easy_setopt(HTTP_HANDLE, CURLOPT_URL, http->url);
+    curl_easy_setopt(HTTP_HANDLE, CURLOPT_HTTPHEADER, http->req_headers);
+    curl_easy_setopt(HTTP_HANDLE, CURLOPT_READDATA, &state);
+    curl_easy_setopt(HTTP_HANDLE, CURLOPT_SEEKDATA, &state);
+    curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEHEADER, &state);
+    curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEDATA, &state);
+
+    if(curl_easy_perform(HTTP_HANDLE) != 0) {
+        JS_ReportErrorUTF8(cx, "Failed to execute HTTP request: %s", ERRBUF);
+        if(state.recvbuf) JS_free(cx, state.recvbuf);
+        return ret;
+    }
+
+    if(!state.resp_headers) {
+        JS_ReportErrorUTF8(cx, "Failed to recieve HTTP headers.");
+        if(state.recvbuf) JS_free(cx, state.recvbuf);
+        return ret;
+    }
+    tmp = JS::ObjectValue(*state.resp_headers);
+    JS::RootedValue rtmp(cx, tmp);
+
+    if(!JS_DefineProperty(
+        cx, robj,
+        "_headers",
+        rtmp,
+        JSPROP_READONLY
+    )) {
+        JS_ReportErrorUTF8(cx, "INTERNAL: Failed to set response headers.");
+        if(state.recvbuf) JS_free(cx, state.recvbuf);
+        return ret;;
+    }
+
+    if(state.recvbuf) {
+        state.recvbuf[state.read] = '\0';
+        jsbody = dec_string(cx, state.recvbuf, state.read+1);
+        if(!jsbody) {
+            // If we can't decode the body as UTF-8 we forcefully
+            // convert it to a string by just forcing each byte
+            // to a char16_t.
+            //jsbody = str_from_binary(cx, state.recvbuf, state.read);
+            jsbody = JS_NewStringCopyN(cx, state.recvbuf, state.read);
+            if(!jsbody) {
+                if(!JS_IsExceptionPending(cx)) {
+                    JS_ReportErrorUTF8(cx, "INTERNAL: Failed to decode body.");
+                }
+                if(state.recvbuf) JS_free(cx, state.recvbuf);
+                return ret;
+            }
+        }
+        tmp = JS::StringValue(jsbody);
+    } else {
+        tmp = JS_GetEmptyStringValue(cx);
+    }
+
+    JS::RootedValue rtmp2(cx, tmp);
+
+    if(!JS_DefineProperty(
+        cx, robj,
+        "responseText",
+        rtmp2,
+        JSPROP_READONLY
+    )) {
+        JS_ReportErrorUTF8(cx, "INTERNAL: Failed to set responseText.");
+        if(state.recvbuf) JS_free(cx, state.recvbuf);
+        return ret;
+    }
+
+    ret = true;
+    if(state.recvbuf) JS_free(cx, state.recvbuf);
+    return ret;
+}
+
+static size_t
+send_body(void *ptr, size_t size, size_t nmem, void *data)
+{
+    CurlState* state = (CurlState*) data;
+    size_t length = size * nmem;
+    size_t towrite = state->sendlen - state->sent;
+
+    // Assume this is cURL trying to resend a request that
+    // failed.
+    if(towrite == 0 && state->sent_once == 0) {
+        state->sent_once = 1;
+        return 0;
+    } else if(towrite == 0) {
+        state->sent = 0;
+        state->sent_once = 0;
+        towrite = state->sendlen;
+    }
+
+    if(length < towrite) towrite = length;
+
+    memcpy(ptr, state->sendbuf + state->sent, towrite);
+    state->sent += towrite;
+
+    return towrite;
+}
+
+static int
+seek_body(void* ptr, curl_off_t offset, int origin)
+{
+    CurlState* state = (CurlState*) ptr;
+    if(origin != SEEK_SET) return -1;
+
+    state->sent = (size_t) offset;
+    return (int) state->sent;
+}
+
+static size_t
+recv_header(void *ptr, size_t size, size_t nmem, void *data)
+{
+    CurlState* state = (CurlState*) data;
+    char code[4];
+    char* header = (char*) ptr;
+    size_t length = size * nmem;
+    JSString* hdr = NULL;
+    uint32_t hdrlen;
+
+    if(length > 7 && strncasecmp(header, "HTTP/1.", 7) == 0) {
+        if(length < 12) {
+            return CURLE_WRITE_ERROR;
+        }
+
+        memcpy(code, header+9, 3*sizeof(char));
+        code[3] = '\0';
+        state->http->last_status = atoi(code);
+
+        state->resp_headers = JS_NewArrayObject(state->cx, 0);
+        if(!state->resp_headers) {
+            return CURLE_WRITE_ERROR;
+        }
+
+        return length;
+    }
+
+    // We get a notice at the \r\n\r\n after headers.
+    if(length <= 2) {
+        return length;
+    }
+
+    // Append the new header to our array.
+    hdr = dec_string(state->cx, header, length);
+    if(!hdr) {
+        return CURLE_WRITE_ERROR;
+    }
+
+    JS::RootedObject obj(state->cx, state->resp_headers);
+    if(!JS_GetArrayLength(state->cx, obj, &hdrlen)) {
+        return CURLE_WRITE_ERROR;
+    }
+
+    JS::RootedString hdrval(state->cx, hdr);
+    if(!JS_SetElement(state->cx, obj, hdrlen, hdrval)) {
+        return CURLE_WRITE_ERROR;
+    }
+
+    return length;
+}
+
+static size_t
+recv_body(void *ptr, size_t size, size_t nmem, void *data)
+{
+    CurlState* state = (CurlState*) data;
+    size_t length = size * nmem;
+    char* tmp = NULL;
+
+    if(!state->recvbuf) {
+        state->recvlen = 4096;
+        state->read = 0;
+        state->recvbuf = static_cast<char *>(JS_malloc(state->cx, state->recvlen));
+    }
+
+    if(!state->recvbuf) {
+        return CURLE_WRITE_ERROR;
+    }
+
+    // +1 so we can add '\0' back up in the go function.
+    size_t oldlen = state->recvlen;
+    while(length+1 > state->recvlen - state->read) state->recvlen *= 2;
+    tmp = static_cast<char *>(JS_realloc(state->cx, state->recvbuf, oldlen, state->recvlen));
+    if(!tmp) return CURLE_WRITE_ERROR;
+    state->recvbuf = tmp;
+
+    memcpy(state->recvbuf + state->read, ptr, length);
+    state->read += length;
+    return length;
+}
+
+/*JSString*
+str_from_binary(JSContext* cx, char* data, size_t length)
+{
+    char16_t* conv = static_cast<char16_t *>(JS_malloc(cx, length * sizeof(char16_t)));
+    JSString* ret = NULL;
+    size_t i;
+
+    if(!conv) return NULL;
+
+    for(i = 0; i < length; i++) {
+        conv[i] = (char16_t) data[i];
+    }
+
+    ret = JS_NewUCString(cx, conv, length);
+    if(!ret) JS_free(cx, conv);
+
+    return ret;
+}
+*/
+
+#endif /* HAVE_CURL */
diff --git a/src/couch/priv/couch_js/68/http.h b/src/couch/priv/couch_js/68/http.h
new file mode 100644
index 0000000..797b3c0
--- /dev/null
+++ b/src/couch/priv/couch_js/68/http.h
@@ -0,0 +1,27 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef COUCH_JS_HTTP_H
+#define COUCH_JS_HTTP_H
+
+#include "util.h"
+
+void http_check_enabled();
+bool http_ctor(JSContext* cx, JSObject* req);
+void http_dtor(JSFreeOp* fop, JSObject* req);
+bool http_open(JSContext* cx, JSObject* req, JS::Value mth, JS::Value url, JS::Value snc);
+bool http_set_hdr(JSContext* cx, JSObject* req, JS::Value name, JS::Value val);
+bool http_send(JSContext* cx, JSObject* req, JS::Value body);
+int http_status(JSContext* cx, JSObject* req);
+bool http_uri(JSContext* cx, JSObject *req, couch_args* args, JS::Value* uri);
+
+#endif
diff --git a/src/couch/priv/couch_js/68/main.cpp b/src/couch/priv/couch_js/68/main.cpp
new file mode 100644
index 0000000..709bab7
--- /dev/null
+++ b/src/couch/priv/couch_js/68/main.cpp
@@ -0,0 +1,499 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef XP_WIN
+#define NOMINMAX
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+#include <jsapi.h>
+#include <js/CompilationAndEvaluation.h>
+#include <js/Conversions.h>
+#include <js/Initialization.h>
+#include <js/SourceText.h>
+#include <js/Warnings.h>
+#include <js/Wrapper.h>
+
+#include "config.h"
+#include "http.h"
+#include "utf8.h"
+#include "util.h"
+
+static bool enableSharedMemory = true;
+
+static JSClassOps global_ops = {
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    JS_GlobalObjectTraceHook
+};
+
+/* The class of the global object. */
+static JSClass global_class = {
+    "global",
+    JSCLASS_GLOBAL_FLAGS,
+    &global_ops
+};
+
+
+static void
+req_dtor(JSFreeOp* fop, JSObject* obj)
+{
+    http_dtor(fop, obj);
+}
+
+// With JSClass.construct.
+static const JSClassOps clsOps = {
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    nullptr,
+    req_dtor,
+    nullptr,
+    nullptr,
+    nullptr
+};
+
+static const JSClass CouchHTTPClass = {
+    "CouchHTTP",  /* name */
+    JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(2),        /* flags */
+    &clsOps
+};
+
+static bool
+req_ctor(JSContext* cx, unsigned int argc, JS::Value* vp)
+{
+    bool ret;
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    JSObject* obj = JS_NewObjectForConstructor(cx, &CouchHTTPClass, args);
+    if(!obj) {
+        JS_ReportErrorUTF8(cx, "Failed to create CouchHTTP instance");
+        return false;
+    }
+    ret = http_ctor(cx, obj);
+    args.rval().setObject(*obj);
+    return ret;
+}
+
+static bool
+req_open(JSContext* cx, unsigned int argc, JS::Value* vp)
+{
+    GET_THIS(cx, argc, vp, args, obj)
+    bool ret = false;
+
+    if(argc == 2) {
+        ret = http_open(cx, obj, args[0], args[1], JS::BooleanValue(false));
+    } else if(argc == 3) {
+        ret = http_open(cx, obj, args[0], args[1], args[2]);
+    } else {
+        JS_ReportErrorUTF8(cx, "Invalid call to CouchHTTP.open");
+    }
+
+    args.rval().setUndefined();
+    return ret;
+}
+
+
+static bool
+req_set_hdr(JSContext* cx, unsigned int argc, JS::Value* vp)
+{
+    GET_THIS(cx, argc, vp, args, obj)
+    bool ret = false;
+
+    if(argc == 2) {
+        ret = http_set_hdr(cx, obj, args[0], args[1]);
+    } else {
+        JS_ReportErrorUTF8(cx, "Invalid call to CouchHTTP.set_header");
+    }
+
+    args.rval().setUndefined();
+    return ret;
+}
+
+
+static bool
+req_send(JSContext* cx, unsigned int argc, JS::Value* vp)
+{
+    GET_THIS(cx, argc, vp, args, obj)
+    bool ret = false;
+
+    if(argc == 1) {
+        ret = http_send(cx, obj, args[0]);
+    } else {
+        JS_ReportErrorUTF8(cx, "Invalid call to CouchHTTP.send");
+    }
+
+    args.rval().setUndefined();
+    return ret;
+}
+
+static bool
+req_status(JSContext* cx, unsigned int argc, JS::Value* vp)
+{
+    GET_THIS(cx, argc, vp, args, obj)
+    int status = http_status(cx, obj);
+
+    if(status < 0)
+        return false;
+
+    args.rval().set(JS::Int32Value(status));
+    return true;
+}
+
+static bool
+base_url(JSContext *cx, unsigned int argc, JS::Value* vp)
+{
+    GET_THIS(cx, argc, vp, args, obj)
+    couch_args *cargs = (couch_args*)JS_GetContextPrivate(cx);
+    JS::Value uri_val;
+    bool rc = http_uri(cx, obj, cargs, &uri_val);
+    args.rval().set(uri_val);
+    return rc;
+}
+
+static JSObject*
+NewSandbox(JSContext* cx, bool lazy)
+{
+    JS::RealmOptions options;
+    options.creationOptions().setSharedMemoryAndAtomicsEnabled(enableSharedMemory);
+    options.creationOptions().setNewCompartmentAndZone();
+    // for debugging
+    // options.creationOptions().setPropertyErrorMessageFixEnabled(enablePropertyErrorMessageFix);
+    JS::RootedObject obj(cx, JS_NewGlobalObject(cx, &global_class, nullptr,
+                                            JS::DontFireOnNewGlobalHook, options));
+    if (!obj)
+        return nullptr;
+
+    {
+        JSAutoRealm ac(cx, obj);
+        if (!lazy && !JS::InitRealmStandardClasses(cx))
+            return nullptr;
+
+        JS::RootedValue value(cx, JS::BooleanValue(lazy));
+        if (!JS_DefineProperty(cx, obj, "lazy", value, JSPROP_PERMANENT | JSPROP_READONLY))
+            return nullptr;
+
+        JS_FireOnNewGlobalObject(cx, obj);
+    }
+
+    if (!JS_WrapObject(cx, &obj))
+        return nullptr;
+    return obj;
+}
+
+static bool
+evalcx(JSContext *cx, unsigned int argc, JS::Value* vp)
+{
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    bool ret = false;
+
+    JS::RootedString str(cx, args[0].toString());
+    if (!str)
+        return false;
+
+    JS::RootedObject sandbox(cx);
+    if (args.hasDefined(1)) {
+        sandbox = JS::ToObject(cx, args[1]);
+        if (!sandbox)
+            return false;
+    }
+
+    if (!sandbox) {
+        sandbox = NewSandbox(cx, false);
+        if (!sandbox)
+            return false;
+    }
+
+    JS::AutoStableStringChars strChars(cx);
+    if (!strChars.initTwoByte(cx, str))
+        return false;
+
+    mozilla::Range<const char16_t> chars = strChars.twoByteRange();
+    JS::SourceText<char16_t> srcBuf;
+    if (!srcBuf.init(cx, chars.begin().get(), chars.length(),
+                     JS::SourceOwnership::Borrowed)) {
+        return false;
+    }
+
+    if(srcBuf.length() == 0) {
+        args.rval().setObject(*sandbox);
+    } else {
+        mozilla::Maybe<JSAutoRealm> ar;
+        unsigned flags;
+        JSObject* unwrapped = UncheckedUnwrap(sandbox, true, &flags);
+        if (flags & js::Wrapper::CROSS_COMPARTMENT) {
+            sandbox = unwrapped;
+            ar.emplace(cx, sandbox);
+        }
+
+        JS::CompileOptions opts(cx);
+        JS::RootedValue rval(cx);
+        opts.setFileAndLine("<unknown>", 1);
+        
+        if (!JS::Evaluate(cx, opts, srcBuf, args.rval())) {
+             return false;
+         }
+    }
+    ret = true;
+    if (!JS_WrapValue(cx, args.rval()))
+        return false;
+
+    return ret;
+}
+
+
+static bool
+gc(JSContext* cx, unsigned int argc, JS::Value* vp)
+{
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    JS_GC(cx);
+    args.rval().setUndefined();
+    return true;
+}
+
+
+static bool
+print(JSContext* cx, unsigned int argc, JS::Value* vp)
+{
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    couch_print(cx, argc, args);
+    args.rval().setUndefined();
+    return true;
+}
+
+
+static bool
+quit(JSContext* cx, unsigned int argc, JS::Value* vp)
+{
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+    int exit_code = args[0].toInt32();;
+    exit(exit_code);
+}
+
+
+static bool
+readline(JSContext* cx, unsigned int argc, JS::Value* vp)
+{
+    JSString* line;
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+    /* GC Occasionally */
+    JS_MaybeGC(cx);
+
+    line = couch_readline(cx, stdin);
+    if(line == NULL) return false;
+
+    // return with JSString* instead of JSValue in the past
+    args.rval().setString(line);
+    return true;
+}
+
+
+static bool
+seal(JSContext* cx, unsigned int argc, JS::Value* vp)
+{
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    JS::RootedObject target(cx);
+    target = JS::ToObject(cx, args[0]);
+    if (!target) {
+        args.rval().setUndefined();
+        return true;
+    }
+    bool deep = false;
+    deep = args[1].toBoolean();
+    bool ret = deep ? JS_DeepFreezeObject(cx, target) : JS_FreezeObject(cx, target);
+    args.rval().setUndefined();
+    return ret;
+}
+
+
+static bool
+js_sleep(JSContext* cx, unsigned int argc, JS::Value* vp)
+{
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+    int duration = args[0].toInt32();
+
+#ifdef XP_WIN
+    Sleep(duration);
+#else
+    usleep(duration * 1000);
+#endif
+
+    return true;
+}
+
+JSPropertySpec CouchHTTPProperties[] = {
+    JS_PSG("status", req_status, 0),
+    JS_PSG("base_url", base_url, 0),
+    JS_PS_END
+};
+
+
+JSFunctionSpec CouchHTTPFunctions[] = {
+    JS_FN("_open", req_open, 3, 0),
+    JS_FN("_setRequestHeader", req_set_hdr, 2, 0),
+    JS_FN("_send", req_send, 1, 0),
+    JS_FS_END
+};
+
+
+JSFunctionSpec TestSuiteFunctions[] = {
+    JS_FN("sleep", js_sleep, 1, 0),
+    JS_FS_END
+};
+
+
+static JSFunctionSpec global_functions[] = {
+    JS_FN("evalcx", evalcx, 0, 0),
+    JS_FN("gc", gc, 0, 0),
+    JS_FN("print", print, 0, 0),
+    JS_FN("quit", quit, 0, 0),
+    JS_FN("readline", readline, 0, 0),
+    JS_FN("seal", seal, 0, 0),
+    JS_FS_END
+};
+
+
+static bool
+csp_allows(JSContext* cx, JS::HandleValue code)
+{
+    couch_args *args = (couch_args*)JS_GetContextPrivate(cx);
+    if(args->eval) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+
+static JSSecurityCallbacks security_callbacks = {
+    csp_allows,
+    nullptr
+};
+
+
+int
+main(int argc, const char* argv[])
+{
+    JSContext* cx = NULL;
+    JSObject* klass = NULL;
+    // char* scriptsrc;
+    // size_t slen;
+    int i;
+
+    couch_args* args = couch_parse_args(argc, argv);
+
+    JS_Init();
+    cx = JS_NewContext(args->stack_size, 8L * 1024L);
+    if(cx == NULL)
+        return 1;
+
+    JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_ENABLE, 0);
+    JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_ENABLE, 0);
+
+    if (!JS::InitSelfHostedCode(cx))
+        return 1;
+
+    JS::SetWarningReporter(cx, couch_error);
+    JS::SetOutOfMemoryCallback(cx, couch_oom, NULL);
+    JS_SetContextPrivate(cx, args);
+    JS_SetSecurityCallbacks(cx, &security_callbacks);
+
+    JS::RealmOptions options;
+    JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr,
+                                                   JS::FireOnNewGlobalHook, options));
+    if (!global)
+        return 1;
+
+    JSAutoRealm ar(cx, global);
+
+    if(!JS::InitRealmStandardClasses(cx))
+        return 1;
+
+    if(couch_load_funcs(cx, global, global_functions) != true)
+        return 1;
+
+    if(args->use_http) {
+        http_check_enabled();
+
+        klass = JS_InitClass(
+            cx, global,
+            NULL,
+            &CouchHTTPClass, req_ctor,
+            0,
+            CouchHTTPProperties, CouchHTTPFunctions,
+            NULL, NULL
+        );
+
+        if(!klass)
+        {
+            fprintf(stderr, "Failed to initialize CouchHTTP class.\n");
+            exit(2);
+        }
+    }
+
+    if(args->use_test_funs) {
+        if(couch_load_funcs(cx, global, TestSuiteFunctions) != true)
+            return 1;
+    }
+
+    for(i = 0 ; args->scripts[i] ; i++) {
+        // slen = couch_readfile(args->scripts[i], &scriptsrc);
+        const char* filename = args->scripts[i];
+
+        // Compile and run
+        JS::CompileOptions options(cx);
+        options.setFileAndLine(filename, 1);
+        JS::RootedScript script(cx);
+        FILE* fp;
+
+        fp = fopen(args->scripts[i], "r");
+        if(fp == NULL) {
+            fprintf(stderr, "Failed to read file: %s\n", filename);
+            return 3;
+        }
+        script = JS::CompileUtf8File(cx, options, fp);
+        fclose(fp);
+        if (!script) {
+            fprintf(stderr, "Failed to compile file: %s\n", filename);
+            return 1;
+        }
+
+        JS::RootedValue result(cx);
+        if(JS_ExecuteScript(cx, script, &result) != true) {
+            fprintf(stderr, "Failed to execute script.\n");
+            return 1;
+        }
+
+        // Give the GC a chance to run.
+        JS_MaybeGC(cx);
+    }
+
+    return 0;
+}
diff --git a/src/couch/priv/couch_js/68/utf8.cpp b/src/couch/priv/couch_js/68/utf8.cpp
new file mode 100644
index 0000000..8879d92
--- /dev/null
+++ b/src/couch/priv/couch_js/68/utf8.cpp
@@ -0,0 +1,310 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <jsapi.h>
+#include <js/Initialization.h>
+#include <js/Conversions.h>
+#include <js/MemoryFunctions.h>
+#include <js/Wrapper.h>
+#include "config.h"
+#include "util.h"
+
+static int
+enc_char(uint8_t *utf8Buffer, uint32_t ucs4Char)
+{
+    int utf8Length = 1;
+
+    if (ucs4Char < 0x80)
+    {
+        *utf8Buffer = (uint8_t)ucs4Char;
+    }
+    else
+    {
+        int i;
+        uint32_t a = ucs4Char >> 11;
+        utf8Length = 2;
+        while(a)
+        {
+            a >>= 5;
+            utf8Length++;
+        }
+        i = utf8Length;
+        while(--i)
+        {
+            utf8Buffer[i] = (uint8_t)((ucs4Char & 0x3F) | 0x80);
+            ucs4Char >>= 6;
+        }
+        *utf8Buffer = (uint8_t)(0x100 - (1 << (8-utf8Length)) + ucs4Char);
+    }
+
+    return utf8Length;
+}
+
+static bool
+enc_charbuf(const char16_t* src, size_t srclen, char* dst, size_t* dstlenp)
+{
+    size_t i;
+    size_t utf8Len;
+    size_t dstlen = *dstlenp;
+    size_t origDstlen = dstlen;
+    char16_t c;
+    char16_t c2;
+    uint32_t v;
+    uint8_t utf8buf[6];
+
+    if(!dst)
+    {
+        dstlen = origDstlen = (size_t) -1;
+    }
+
+    while(srclen)
+    {
+        c = *src++;
+        srclen--;
+
+        if(c <= 0xD7FF || c >= 0xE000)
+        {
+            v = (uint32_t) c;
+        }
+        else if(c >= 0xD800 && c <= 0xDBFF)
+        {
+            if(srclen < 1) goto buffer_too_small;
+            c2 = *src++;
+            srclen--;
+            if(c2 >= 0xDC00 && c2 <= 0xDFFF)
+            {
+                v = (uint32_t) (((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000);
+            }
+            else
+            {
+                // Invalid second half of surrogate pair
+                v = (uint32_t) 0xFFFD;
+                // Undo our character advancement
+                src--;
+                srclen++;
+            }
+        }
+        else
+        {
+            // Invalid first half surrogate pair
+            v = (uint32_t) 0xFFFD;
+        }
+
+        if(v < 0x0080)
+        {
+            // no encoding necessary - performance hack
+            if(!dstlen) goto buffer_too_small;
+            if(dst) *dst++ = (char) v;
+            utf8Len = 1;
+        }
+        else
+        {
+            utf8Len = enc_char(utf8buf, v);
+            if(utf8Len > dstlen) goto buffer_too_small;
+            if(dst)
+            {
+                for (i = 0; i < utf8Len; i++)
+                {
+                    *dst++ = (char) utf8buf[i];
+                }
+            }
+        }
+        dstlen -= utf8Len;
+    }
+    
+    *dstlenp = (origDstlen - dstlen);
+    return true;
+
+buffer_too_small:
+    *dstlenp = (origDstlen - dstlen);
+    return false;
+}
+
+char*
+enc_string(JSContext* cx, JS::Value arg, size_t* buflen)
+{
+    JSString* str = NULL;
+    const char16_t* src = NULL;
+    char* bytes = NULL;
+    size_t srclen = 0;
+    size_t byteslen = 0;
+    JS::AutoStableStringChars rawChars(cx);
+    
+    str = arg.toString();
+    if(!str) goto error;
+
+    if (!rawChars.initTwoByte(cx, str))
+        return NULL;
+
+    src = rawChars.twoByteRange().begin().get();
+    srclen = JS_GetStringLength(str);
+
+    if(!enc_charbuf(src, srclen, NULL, &byteslen)) goto error;
+    
+    //bytes = static_cast<char *>(JS_malloc(cx, (byteslen) + 1));
+    bytes = js_pod_malloc<char>(byteslen + 1);
+    bytes[byteslen] = 0;
+    
+    if(!enc_charbuf(src, srclen, bytes, &byteslen)) goto error;
+
+    if(buflen) *buflen = byteslen;
+    goto success;
+
+error:
+    if(bytes != NULL) JS_free(cx, bytes);
+    bytes = NULL;
+
+success:
+/*
+    JS::RootedString str(cx, arg.toString());
+    JS::UniqueChars chars = JS_EncodeStringToUTF8(cx, str);
+
+    if(buflen) *buflen = strlen(chars.get());
+    
+    return JS_NewUCStringCopyN(cs, chars.get(), buflen);
+*/
+    return bytes;
+}
+
+static uint32_t
+dec_char(const uint8_t *utf8Buffer, int utf8Length)
+{
+    uint32_t ucs4Char;
+    uint32_t minucs4Char;
+
+    // from Unicode 3.1, non-shortest form is illegal
+    static const uint32_t minucs4Table[] = {
+        0x00000080, 0x00000800, 0x0001000, 0x0020000, 0x0400000
+    };
+
+    if (utf8Length == 1)
+    {
+        ucs4Char = *utf8Buffer;
+    }
+    else
+    {
+        ucs4Char = *utf8Buffer++ & ((1<<(7-utf8Length))-1);
+        minucs4Char = minucs4Table[utf8Length-2];
+        while(--utf8Length)
+        {
+            ucs4Char = ucs4Char<<6 | (*utf8Buffer++ & 0x3F);
+        }
+        if(ucs4Char < minucs4Char || ucs4Char == 0xFFFE || ucs4Char == 0xFFFF)
+        {
+            ucs4Char = 0xFFFD;
+        }
+    }
+
+    return ucs4Char;
+}
+
+static bool
+dec_charbuf(const char *src, size_t srclen, char16_t *dst, size_t *dstlenp)
+{
+    uint32_t v;
+    size_t offset = 0;
+    size_t j;
+    size_t n;
+    size_t dstlen = *dstlenp;
+    size_t origDstlen = dstlen;
+
+    if(!dst) dstlen = origDstlen = (size_t) -1;
+
+    while(srclen)
+    {
+        v = (uint8_t) *src;
+        n = 1;
+        
+        if(v & 0x80)
+        {
+            while(v & (0x80 >> n))
+            {
+                n++;
+            }
+            
+            if(n > srclen) goto buffer_too_small;
+            if(n == 1 || n > 6) goto bad_character;
+            
+            for(j = 1; j < n; j++)
+            {
+                if((src[j] & 0xC0) != 0x80) goto bad_character;
+            }
+
+            v = dec_char((const uint8_t *) src, n);
+            if(v >= 0x10000)
+            {
+                v -= 0x10000;
+                
+                if(v > 0xFFFFF || dstlen < 2)
+                {
+                    *dstlenp = (origDstlen - dstlen);
+                    return false;
+                }
+                
+                if(dstlen < 2) goto buffer_too_small;
+
+                if(dst)
+                {
+                    *dst++ = (char16_t)((v >> 10) + 0xD800);
+                    v = (char16_t)((v & 0x3FF) + 0xDC00);
+                }
+                dstlen--;
+            }
+        }
+
+        if(!dstlen) goto buffer_too_small;
+        if(dst) *dst++ = (char16_t) v;
+
+        dstlen--;
+        offset += n;
+        src += n;
+        srclen -= n;
+    }
+
+    *dstlenp = (origDstlen - dstlen);
+    return true;
+
+bad_character:
+    *dstlenp = (origDstlen - dstlen);
+    return false;
+
+buffer_too_small:
+    *dstlenp = (origDstlen - dstlen);
+    return false;
+}
+
+JSString*
+dec_string(JSContext* cx, const char* bytes, size_t byteslen)
+{
+    JSString* str = NULL;
+    size_t charslen;
+    
+    if(!dec_charbuf(bytes, byteslen, NULL, &charslen)) return NULL;
+
+    JS::UniqueTwoByteChars chars(js_pod_malloc<char16_t>(charslen + 1));
+    if(!chars) return NULL;
+    chars.get()[charslen] = 0;
+
+    if(!dec_charbuf(bytes, byteslen, chars.get(), &charslen)) goto error;
+
+    str = JS_NewUCString(cx, std::move(chars), charslen - 1);
+    if(!str) goto error;
+
+    goto success;
+
+error:
+    if(chars != NULL) JS_free(cx, chars.get());
+    str = NULL;
+
+success:
+    return str;
+}
diff --git a/src/couch/priv/couch_js/68/utf8.h b/src/couch/priv/couch_js/68/utf8.h
new file mode 100644
index 0000000..c8b1f4d
--- /dev/null
+++ b/src/couch/priv/couch_js/68/utf8.h
@@ -0,0 +1,19 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef COUCH_JS_UTF_8_H
+#define COUCH_JS_UTF_8_H
+
+char* enc_string(JSContext* cx, JS::Value arg, size_t* buflen);
+JSString* dec_string(JSContext* cx, const char* buf, size_t buflen);
+
+#endif
diff --git a/src/couch/priv/couch_js/68/util.cpp b/src/couch/priv/couch_js/68/util.cpp
new file mode 100644
index 0000000..b9c9326
--- /dev/null
+++ b/src/couch/priv/couch_js/68/util.cpp
@@ -0,0 +1,351 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <jsapi.h>
+#include <js/Conversions.h>
+#include <js/Initialization.h>
+#include <js/MemoryFunctions.h>
+#include <js/RegExp.h>
+
+#include "help.h"
+#include "util.h"
+#include "utf8.h"
+
+/*
+std::string
+js_to_string(JSContext* cx, JS::HandleValue val)
+{
+    JS::RootedString sval(cx);
+    sval = val.toString();
+
+    JS::UniqueChars chars(JS_EncodeStringToUTF8(cx, sval));
+    if(!chars) {
+        JS_ClearPendingException(cx);
+        fprintf(stderr, "Error converting value to string.\n");
+        exit(3);
+    }
+
+    return chars.get();
+}
+
+std::string
+js_to_string(JSContext* cx, JSString *str)
+{
+    JS::UniqueChars chars(JS_EncodeString(cx, str));
+    if(!chars) {
+        JS_ClearPendingException(cx);
+        fprintf(stderr, "Error converting  to string.\n");
+        exit(3);
+    }
+
+    return chars.get();
+}
+*/
+
+JSString*
+string_to_js(JSContext* cx, const std::string& s)
+{
+/*
+
+    JSString* ret = JS_NewStringCopyN(cx, s.c_str(), s.size());
+    if(ret != nullptr) {
+        return ret;
+    }
+
+    fprintf(stderr, "Unable to allocate string object.\n");
+    exit(3);
+*/
+    return dec_string(cx, s.c_str(), s.size());
+}
+
+size_t
+couch_readfile(const char* file, char** outbuf_p)
+{
+    FILE* fp;
+    char fbuf[16384];
+    char *buf = NULL;
+    char* tmp;
+    size_t nread = 0;
+    size_t buflen = 0;
+
+    if(strcmp(file, "-") == 0) {
+        fp = stdin;
+    } else {
+        fp = fopen(file, "r");
+        if(fp == NULL) {
+            fprintf(stderr, "Failed to read file: %s\n", file);
+            exit(3);
+        }
+    }
+
+    while((nread = fread(fbuf, 1, 16384, fp)) > 0) {
+        if(buf == NULL) {
+            buf = (char*) malloc(nread + 1);
+            if(buf == NULL) {
+                fprintf(stderr, "Out of memory.\n");
+                exit(3);
+            }
+            memcpy(buf, fbuf, nread);
+        } else {
+            tmp = (char*) malloc(buflen + nread + 1);
+            if(tmp == NULL) {
+                fprintf(stderr, "Out of memory.\n");
+                exit(3);
+            }
+            memcpy(tmp, buf, buflen);
+            memcpy(tmp+buflen, fbuf, nread);
+            free(buf);
+            buf = tmp;
+        }
+        buflen += nread;
+        buf[buflen] = '\0';
+    }
+    *outbuf_p = buf;
+    return buflen ;
+}
+
+couch_args*
+couch_parse_args(int argc, const char* argv[])
+{
+    couch_args* args;
+    int i = 1;
+
+    args = (couch_args*) malloc(sizeof(couch_args));
+    if(args == NULL)
+        return NULL;
+
+    memset(args, '\0', sizeof(couch_args));
+    args->stack_size = 64L * 1024L * 1024L;
+
+    while(i < argc) {
+        if(strcmp("-h", argv[i]) == 0) {
+            DISPLAY_USAGE;
+            exit(0);
+        } else if(strcmp("-V", argv[i]) == 0) {
+            DISPLAY_VERSION;
+            exit(0);
+        } else if(strcmp("-H", argv[i]) == 0) {
+            args->use_http = 1;
+        } else if(strcmp("-T", argv[i]) == 0) {
+            args->use_test_funs = 1;
+        } else if(strcmp("-S", argv[i]) == 0) {
+            args->stack_size = atoi(argv[++i]);
+            if(args->stack_size <= 0) {
+                fprintf(stderr, "Invalid stack size.\n");
+                exit(2);
+            }
+        } else if(strcmp("-u", argv[i]) == 0) {
+            args->uri_file = argv[++i];
+        } else if(strcmp("--eval", argv[i]) == 0) {
+            args->eval = 1;
+        } else if(strcmp("--", argv[i]) == 0) {
+            i++;
+            break;
+        } else {
+            break;
+        }
+        i++;
+    }
+
+    if(i >= argc) {
+        DISPLAY_USAGE;
+        exit(3);
+    }
+    args->scripts = argv + i;
+
+    return args;
+}
+
+
+int
+couch_fgets(char* buf, int size, FILE* fp)
+{
+    int n, i, c;
+
+    if(size <= 0) return -1;
+    n = size - 1;
+
+    for(i = 0; i < n && (c = getc(fp)) != EOF; i++) {
+        buf[i] = c;
+        if(c == '\n') {
+            i++;
+            break;
+        }
+    }
+
+    buf[i] = '\0';
+    return i;
+}
+
+
+JSString*
+couch_readline(JSContext* cx, FILE* fp)
+{
+    JSString* str;
+    char* bytes = NULL;
+    char* tmp = NULL;
+    size_t used = 0;
+    size_t byteslen = 256;
+    size_t oldbyteslen = 256;
+    size_t readlen = 0;
+    bool sawNewline = false;
+
+    bytes = static_cast<char *>(JS_malloc(cx, byteslen));
+    if(bytes == NULL) return NULL;
+    
+    while((readlen = couch_fgets(bytes+used, byteslen-used, fp)) > 0) {
+        used += readlen;
+        
+        if(bytes[used-1] == '\n') {
+            bytes[used-1] = '\0';
+            sawNewline = true;
+            break;
+        }
+        
+        // Double our buffer and read more.
+        oldbyteslen = byteslen;
+        byteslen *= 2;
+        tmp = static_cast<char *>(JS_realloc(cx, bytes, oldbyteslen, byteslen));
+        if(!tmp) {
+            JS_free(cx, bytes);
+            return NULL;
+        }
+        
+        bytes = tmp;
+    }
+
+    // Treat empty strings specially
+    if(used == 0) {
+        JS_free(cx, bytes);
+        return JS_NewStringCopyZ(cx, nullptr);
+    }
+
+    // Shrink the buffer to the actual data size
+    tmp = static_cast<char *>(JS_realloc(cx, bytes, byteslen, used));
+    if(!tmp) {
+        JS_free(cx, bytes);
+        return NULL;
+    }
+    bytes = tmp;
+    byteslen = used;
+
+    str = string_to_js(cx, std::string(tmp, byteslen));
+    //str = JS_NewStringCopyN(cx, bytes, sawNewline ? byteslen - 1 : byteslen);
+    JS_free(cx, bytes);
+    return str;
+}
+
+
+void
+couch_print(JSContext* cx, unsigned int argc, JS::CallArgs argv)
+{
+    FILE *stream = stdout;
+
+    if (argc) {
+        if (argc > 1 && argv[1].isTrue()) {
+          stream = stderr;
+        }
+        JS::AutoSaveExceptionState exc_state(cx);
+        JS::RootedString sval(cx, JS::ToString(cx, argv[0]));
+        exc_state.restore();
+        if (!sval) {
+            fprintf(stream, "couch_print: <cannot convert value to string>\n");
+            fflush(stream);
+            return;
+        }
+        JS::UniqueChars bytes(JS_EncodeStringToUTF8(cx, sval));
+        if (!bytes)
+            return;
+
+        fprintf(stream, "%s", bytes.get());
+    }
+
+    fputc('\n', stream);
+    fflush(stream);
+}
+
+
+void
+couch_error(JSContext* cx, JSErrorReport* report)
+{
+    JS::RootedValue v(cx), stack(cx), replace(cx);
+    char* bytes;
+    JSObject* regexp;
+
+    if(!report || !JSREPORT_IS_WARNING(report->flags))
+    {
+        fprintf(stderr, "%s\n", report->message().c_str());
+
+        // Print a stack trace, if available.
+        if (JSREPORT_IS_EXCEPTION(report->flags) &&
+            JS_GetPendingException(cx, &v))
+        {
+            // Clear the exception before an JS method calls or the result is
+            // infinite, recursive error report generation.
+            JS_ClearPendingException(cx);
+
+            // Use JS regexp to indent the stack trace.
+            // If the regexp can't be created, don't JS_ReportErrorUTF8 since it is
+            // probably not productive to wind up here again.
+            JS::RootedObject vobj(cx, v.toObjectOrNull());
+
+            if(JS_GetProperty(cx, vobj, "stack", &stack) &&
+               (regexp = JS::NewRegExpObject(
+                   cx, "^(?=.)", 6, JS::RegExpFlag::Global | JS::RegExpFlag::Multiline)))
+
+            {
+                // Set up the arguments to ``String.replace()``
+                JS::RootedValueVector re_args(cx);
+                JS::RootedValue arg0(cx, JS::ObjectValue(*regexp));
+                auto arg1 = JS::StringValue(string_to_js(cx, "\t"));
+
+                if (re_args.append(arg0) && re_args.append(arg1)) {
+                    // Perform the replacement
+                    JS::RootedObject sobj(cx, stack.toObjectOrNull());
+                    if(JS_GetProperty(cx, sobj, "replace", &replace) &&
+                       JS_CallFunctionValue(cx, sobj, replace, re_args, &v))
+                    {
+                        // Print the result
+                        bytes = enc_string(cx, v, NULL);
+                        fprintf(stderr, "Stacktrace:\n%s", bytes);
+                        JS_free(cx, bytes);
+                    }
+                }
+            }
+        }
+    }
+}
+
+
+void
+couch_oom(JSContext* cx, void* data)
+{
+    fprintf(stderr, "out of memory\n");
+    exit(1);
+}
+
+
+bool
+couch_load_funcs(JSContext* cx, JS::HandleObject obj, JSFunctionSpec* funcs)
+{
+    JSFunctionSpec* f;
+    for(f = funcs; f->name; f++) {
+        if(!JS_DefineFunction(cx, obj, f->name.string(), f->call.op, f->nargs, f->flags)) {
+            fprintf(stderr, "Failed to create function: %s\n", f->name.string());
+            return false;
+        }
+    }
+    return true;
+}
diff --git a/src/couch/priv/couch_js/68/util.h b/src/couch/priv/couch_js/68/util.h
new file mode 100644
index 0000000..dc8a3a7
--- /dev/null
+++ b/src/couch/priv/couch_js/68/util.h
@@ -0,0 +1,60 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef COUCHJS_UTIL_H
+#define COUCHJS_UTIL_H
+
+#include <jsapi.h>
+
+typedef struct {
+    int          eval;
+    int          use_http;
+    int          use_test_funs;
+    int          stack_size;
+    const char** scripts;
+    const char*  uri_file;
+    JSString*    uri;
+} couch_args;
+
+/*
+std::string js_to_string(JSContext* cx, JS::HandleValue val);
+std::string js_to_string(JSContext* cx, JSString *str);
+JSString* string_to_js(JSContext* cx, const std::string& s);
+*/
+
+couch_args* couch_parse_args(int argc, const char* argv[]);
+int couch_fgets(char* buf, int size, FILE* fp);
+JSString* couch_readline(JSContext* cx, FILE* fp);
+size_t couch_readfile(const char* file, char** outbuf_p);
+void couch_print(JSContext* cx, unsigned int argc, JS::CallArgs argv);
+void couch_error(JSContext* cx, JSErrorReport* report);
+void couch_oom(JSContext* cx, void* data);
+bool couch_load_funcs(JSContext* cx, JS::HandleObject obj, JSFunctionSpec* funcs);
+
+/*
+ * GET_THIS:
+ * @cx: JSContext pointer passed into JSNative function
+ * @argc: Number of arguments passed into JSNative function
+ * @vp: Argument value array passed into JSNative function
+ * @args: Name for JS::CallArgs variable defined by this code snippet
+ * @to: Name for JS::RootedObject variable referring to function's this
+ *
+ * A convenience macro for getting the 'this' object a function was called with.
+ * Use in any JSNative function.
+ */
+#define GET_THIS(cx, argc, vp, args, to)              \
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \
+    JS::RootedObject to(cx);                          \
+    if (!args.computeThis(cx, &to))                   \
+        return false;
+
+#endif // Included util.h
diff --git a/src/couch/rebar.config.script b/src/couch/rebar.config.script
index 91e24d9..89c652a 100644
--- a/src/couch/rebar.config.script
+++ b/src/couch/rebar.config.script
@@ -22,7 +22,7 @@ CopyIfDifferent = fun(Path, Contents) ->
         false ->
             file:write_file(Path, Contents)
     end
-end,
+end.
 
 
 CouchJSName = case os:type() of
@@ -30,21 +30,21 @@ CouchJSName = case os:type() of
         "couchjs.exe";
     _ ->
         "couchjs"
-end,
-CouchJSPath = filename:join(["priv", CouchJSName]),
+end.
+CouchJSPath = filename:join(["priv", CouchJSName]).
 Version = case os:getenv("COUCHDB_VERSION") of
     false ->
         string:strip(os:cmd("git describe --always"), right, $\n);
     Version0 ->
         string:strip(Version0, right)
-end,
+end.
 
 GitSha = case os:getenv("COUCHDB_GIT_SHA") of
     false ->
         ""; % release builds won’t get a fallback
     GitSha0 ->
         string:strip(GitSha0, right)
-end,
+end.
 
 CouchConfig = case filelib:is_file(os:getenv("COUCHDB_CONFIG")) of
     true ->
@@ -59,6 +59,8 @@ SMVsn = case lists:keyfind(spidermonkey_version, 1, CouchConfig) of
         "1.8.5";
     {_, "60"} ->
         "60";
+    {_, "68"} ->
+        "68";
     undefined ->
         "1.8.5";
     {_, Unsupported} ->
@@ -78,24 +80,24 @@ ConfigH = [
     {"PACKAGE_NAME", "\"Apache CouchDB\""},
     {"PACKAGE_STRING", "\"Apache CouchDB " ++ Version ++ "\""},
     {"PACKAGE_VERSION", "\"" ++ Version ++ "\""}
-],
+].
 
-CouchJSConfig = "priv/couch_js/" ++ SMVsn ++ "/config.h",
-ConfigSrc = [["#define ", K, " ", V, $\n] || {K, V} <- ConfigH],
-ConfigBin = iolist_to_binary(ConfigSrc),
-ok = CopyIfDifferent(CouchJSConfig, ConfigBin),
+CouchJSConfig = "priv/couch_js/" ++ SMVsn ++ "/config.h".
+ConfigSrc = [["#define ", K, " ", V, $\n] || {K, V} <- ConfigH].
+ConfigBin = iolist_to_binary(ConfigSrc).
+ok = CopyIfDifferent(CouchJSConfig, ConfigBin).
 
 MD5Config = case lists:keyfind(erlang_md5, 1, CouchConfig) of
     {erlang_md5, true} ->
         [{d, 'ERLANG_MD5', true}];
     _ ->
         []
-end,
+end.
 
 ProperConfig = case code:lib_dir(proper) of
     {error, bad_name} -> [];
     _ -> [{d, 'WITH_PROPER'}]
-end,
+end.
 
 {JS_CFLAGS, JS_LDFLAGS} = case os:type() of
     {win32, _} when SMVsn == "1.8.5" ->
@@ -122,6 +124,11 @@ end,
         {
             "-DXP_UNIX -I/usr/include/mozjs-60 -I/usr/local/include/mozjs-60 -std=c++14",
             "-L/usr/local/lib -std=c++14 -lmozjs-60 -lm"
+        };
+    {unix, _} when SMVsn == "68" ->
+        {
+            "-DXP_UNIX -I/usr/include/mozjs-68 -I/usr/local/include/mozjs-68 -std=c++14 -Wno-invalid-offsetof",
+            "-L/usr/local/lib -std=c++14 -lmozjs-68 -lm"
         }
 end.
 
@@ -146,11 +153,12 @@ end.
         end;
     _ ->
         {"", ""}
-end,
+end.
 
 CouchJSSrc = case SMVsn of
     "1.8.5" -> ["priv/couch_js/1.8.5/*.c"];
-    "60" -> ["priv/couch_js/60/*.cpp"]
+    "60" -> ["priv/couch_js/60/*.cpp"];
+    "68" -> ["priv/couch_js/68/*.cpp"]
 end.
 
 CouchJSEnv = case SMVsn of
@@ -159,26 +167,26 @@ CouchJSEnv = case SMVsn of
             {"CFLAGS", JS_CFLAGS ++ " " ++ CURL_CFLAGS},
             {"LDFLAGS", JS_LDFLAGS ++ " " ++ CURL_LDFLAGS}
         ];
-    "60" ->
+    _ ->
         [
             {"CXXFLAGS", JS_CFLAGS ++ " " ++ CURL_CFLAGS},
             {"LDFLAGS", JS_LDFLAGS ++ " " ++ CURL_LDFLAGS}
         ]
-end,
+end.
 
-IcuPath = "priv/couch_icu_driver.so",
-IcuSrc = ["priv/icu_driver/*.c"],
+IcuPath = "priv/couch_icu_driver.so".
+IcuSrc = ["priv/icu_driver/*.c"].
 IcuEnv = [{"DRV_CFLAGS",  "$DRV_CFLAGS -DPIC -O2 -fno-common"},
-          {"DRV_LDFLAGS", "$DRV_LDFLAGS -lm -licuuc -licudata -licui18n -lpthread"}],
+          {"DRV_LDFLAGS", "$DRV_LDFLAGS -lm -licuuc -licudata -licui18n -lpthread"}].
 IcuDarwinEnv = [{"CFLAGS", "-DXP_UNIX -I/usr/local/opt/icu4c/include"},
-                {"LDFLAGS", "-L/usr/local/opt/icu4c/lib"}],
+                {"LDFLAGS", "-L/usr/local/opt/icu4c/lib"}].
 IcuBsdEnv = [{"CFLAGS", "-DXP_UNIX -I/usr/local/include"},
-             {"LDFLAGS", "-L/usr/local/lib"}],
+             {"LDFLAGS", "-L/usr/local/lib"}].
 IcuWinEnv = [{"CFLAGS", "$DRV_CFLAGS /DXP_WIN"},
-             {"LDFLAGS", "icuin.lib icudt.lib icuuc.lib"}],
+             {"LDFLAGS", "icuin.lib icudt.lib icuuc.lib"}].
 
-ComparePath = "priv/couch_ejson_compare.so",
-CompareSrc = ["priv/couch_ejson_compare/*.c"],
+ComparePath = "priv/couch_ejson_compare.so".
+CompareSrc = ["priv/couch_ejson_compare/*.c"].
 
 BaseSpecs = [
         %% couchjs
@@ -193,17 +201,17 @@ BaseSpecs = [
         {"linux",  ComparePath, CompareSrc, [{env, IcuEnv}]},
         {"bsd",   ComparePath, CompareSrc, [{env, IcuEnv ++ IcuBsdEnv}]},
         {"win32",  ComparePath, CompareSrc, [{env, IcuWinEnv}]}
-],
+].
 
 SpawnSpec = [
     {"priv/couchspawnkillable", ["priv/spawnkillable/*.c"]}
-],
+].
 
 %% hack required until switch to enc/rebar3
 PortEnvOverrides = [
     {"win32", "EXE_LINK_CXX_TEMPLATE",
     "$LINKER $PORT_IN_FILES $LDFLAGS $EXE_LDFLAGS /OUT:$PORT_OUT_FILE"}
-],
+].
 
 PortSpecs = case os:type() of
     {win32, _} ->
@@ -213,10 +221,10 @@ PortSpecs = case os:type() of
         ok = CopyIfDifferent("priv/couchspawnkillable", CSK),
         os:cmd("chmod +x priv/couchspawnkillable"),
         BaseSpecs
-end,
+end.
 PlatformDefines = [
    {platform_define, "win32", 'WINDOWS'}
-],
+].
 AddConfig = [
     {port_specs, PortSpecs},
     {erl_opts, PlatformDefines ++ [
diff --git a/support/build_js.escript b/support/build_js.escript
index 90ad316..2d9de61 100644
--- a/support/build_js.escript
+++ b/support/build_js.escript
@@ -70,6 +70,12 @@ main([]) ->
                 "share/server/60/esprima.js",
                 "share/server/60/escodegen.js",
                 "share/server/60/rewrite_fun.js"
+            ];
+        "68" ->
+            [
+                "share/server/60/esprima.js",
+                "share/server/60/escodegen.js",
+                "share/server/60/rewrite_fun.js"
             ]
     end,