diff --git a/src/ecmascript/spidermonkey-shared.c b/src/ecmascript/spidermonkey-shared.c index 769a35b36..161b99004 100644 --- a/src/ecmascript/spidermonkey-shared.c +++ b/src/ecmascript/spidermonkey-shared.c @@ -9,6 +9,54 @@ #include "ecmascript/spidermonkey-shared.h" +/** A shared runtime used for both user scripts (scripting/smjs/) and + * scripts on web pages (ecmascript/spidermonkey/). + * + * SpiderMonkey has bugs that corrupt memory when multiple JSRuntimes + * are used: https://bugzilla.mozilla.org/show_bug.cgi?id=378918 and + * perhaps others. */ +JSRuntime *spidermonkey_runtime; + +/** A reference count for ::spidermonkey_runtime so that modules using + * it can be initialized and shut down in arbitrary order. */ +static int spidermonkey_runtime_refcount; + +/** Add a reference to ::spidermonkey_runtime, and initialize it if + * that is the first reference. + * @return 1 if successful or 0 on error. If this succeeds, the + * caller must eventually call spidermonkey_runtime_release(). */ +int +spidermonkey_runtime_addref(void) +{ + if (!spidermonkey_runtime) { + assert(spidermonkey_runtime_refcount == 0); + if_assert_failed return 0; + spidermonkey_runtime = JS_NewRuntime(1L * 1024L * 1024L); + if (!spidermonkey_runtime) return 0; + } + spidermonkey_runtime_refcount++; + assert(spidermonkey_runtime_refcount > 0); + if_assert_failed { spidermonkey_runtime_refcount--; return 0; } + return 1; +} + +/** Release a reference to ::spidermonkey_runtime, and destroy it if + * that was the last reference. If spidermonkey_runtime_addref() + * failed, then this must not be called. */ +void +spidermonkey_runtime_release(void) +{ + assert(spidermonkey_runtime_refcount > 0); + assert(spidermonkey_runtime); + if_assert_failed return; + + --spidermonkey_runtime_refcount; + if (spidermonkey_runtime_refcount == 0) { + JS_DestroyRuntime(spidermonkey_runtime); + spidermonkey_runtime = NULL; + } +} + /** An ELinks-specific replacement for JS_DefineFunctions(). * * @relates spidermonkeyFunctionSpec */ diff --git a/src/ecmascript/spidermonkey-shared.h b/src/ecmascript/spidermonkey-shared.h index 3b2602b6b..225c5f46a 100644 --- a/src/ecmascript/spidermonkey-shared.h +++ b/src/ecmascript/spidermonkey-shared.h @@ -24,6 +24,10 @@ #include "util/string.h" +extern JSRuntime *spidermonkey_runtime; +int spidermonkey_runtime_addref(void); +void spidermonkey_runtime_release(void); + /** An ELinks-specific replacement for JSFunctionSpec. * * Bug 1016: In SpiderMonkey 1.7 bundled with XULRunner 1.8, jsapi.h diff --git a/src/scripting/smjs/core.c b/src/scripting/smjs/core.c index 60fe7ba01..a5235c1b3 100644 --- a/src/scripting/smjs/core.c +++ b/src/scripting/smjs/core.c @@ -68,8 +68,6 @@ reported: JS_ClearPendingException(ctx); } -static JSRuntime *smjs_rt; - static int smjs_do_file(unsigned char *path) { @@ -127,13 +125,11 @@ smjs_load_hooks(void) void init_smjs(struct module *module) { - smjs_rt = JS_NewRuntime(1L * 1024L * 1024L); - if (!smjs_rt) return; + if (!spidermonkey_runtime_addref()) return; - smjs_ctx = JS_NewContext(smjs_rt, 8192); + smjs_ctx = JS_NewContext(spidermonkey_runtime, 8192); if (!smjs_ctx) { - JS_DestroyRuntime(smjs_rt); - smjs_rt = NULL; + spidermonkey_runtime_release(); return; } @@ -154,9 +150,15 @@ cleanup_smjs(struct module *module) { if (!smjs_ctx) return; - /* These calls also finalize all JSObjects that have been - * allocated in the JSRuntime, so cache_entry_finalize gets - * called and resets each cache_entry.jsobject = NULL. */ + /* JS_DestroyContext also collects garbage in the JSRuntime. + * Because the JSObjects created in smjs_ctx have not been + * made visible to any other JSContext, and the garbage + * collector of SpiderMonkey is precise, SpiderMonkey + * finalizes all of those objects, so cache_entry_finalize + * gets called and resets each cache_entry.jsobject = NULL. + * If the garbage collector were conservative, ELinks would + * have to call smjs_detach_cache_entry_object on each cache + * entry before it releases the runtime here. */ JS_DestroyContext(smjs_ctx); - JS_DestroyRuntime(smjs_rt); + spidermonkey_runtime_release(); }