Index: ps/trunk/source/graphics/MapGenerator.cpp =================================================================== --- ps/trunk/source/graphics/MapGenerator.cpp +++ ps/trunk/source/graphics/MapGenerator.cpp @@ -92,7 +92,7 @@ shared_ptr mapgenRuntime = ScriptRuntime::CreateRuntime(RMS_RUNTIME_SIZE); // Enable the script to be aborted - JS_SetInterruptCallback(mapgenRuntime->m_rt, MapGeneratorInterruptCallback); + JS_SetInterruptCallback(mapgenRuntime->GetJSRuntime(), MapGeneratorInterruptCallback); self->m_ScriptInterface = new ScriptInterface("Engine", "MapGenerator", mapgenRuntime); @@ -143,7 +143,7 @@ RegisterScriptFunctions_MapGenerator(); // Copy settings to global variable - JS::RootedValue global(rq.cx, m_ScriptInterface->GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); if (!m_ScriptInterface->SetProperty(global, "g_MapSettings", settingsVal, true, true)) { LOGERROR("CMapGeneratorWorker::Run: Failed to define g_MapSettings"); Index: ps/trunk/source/gui/CGUI.h =================================================================== --- ps/trunk/source/gui/CGUI.h +++ ps/trunk/source/gui/CGUI.h @@ -237,7 +237,6 @@ const CGUIColor& GetPreDefinedColor(const CStr& name) const { return m_PreDefinedColors.at(name); } shared_ptr GetScriptInterface() { return m_ScriptInterface; }; - JS::Value GetGlobalObject() { return m_ScriptInterface->GetGlobalObject(); }; private: /** Index: ps/trunk/source/gui/CGUI.cpp =================================================================== --- ps/trunk/source/gui/CGUI.cpp +++ ps/trunk/source/gui/CGUI.cpp @@ -97,8 +97,8 @@ { ret = IN_HANDLED; - ScriptInterface::Request rq(*m_ScriptInterface); - JS::RootedObject globalObj(rq.cx, &GetGlobalObject().toObject()); + ScriptInterface::Request rq(m_ScriptInterface); + JS::RootedObject globalObj(rq.cx, rq.glob); JS::RootedValue result(rq.cx); JS_CallFunctionValue(rq.cx, globalObj, m_GlobalHotkeys[hotkey][eventName], JS::HandleValueArray::empty(), &result); } Index: ps/trunk/source/gui/GUIManager.cpp =================================================================== --- ps/trunk/source/gui/GUIManager.cpp +++ ps/trunk/source/gui/GUIManager.cpp @@ -133,9 +133,9 @@ if (gui) { shared_ptr scriptInterface = gui->GetScriptInterface(); - ScriptInterface::Request rq(*scriptInterface); + ScriptInterface::Request rq(scriptInterface); - JS::RootedValue global(rq.cx, scriptInterface->GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); JS::RootedValue hotloadDataVal(rq.cx); scriptInterface->CallFunction(global, "getHotloadData", &hotloadDataVal); hotloadData = scriptInterface->WriteStructuredClone(hotloadDataVal); @@ -199,11 +199,11 @@ gui->LoadedXmlFiles(); shared_ptr scriptInterface = gui->GetScriptInterface(); - ScriptInterface::Request rq(*scriptInterface); + ScriptInterface::Request rq(scriptInterface); JS::RootedValue initDataVal(rq.cx); JS::RootedValue hotloadDataVal(rq.cx); - JS::RootedValue global(rq.cx, scriptInterface->GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); if (initData) scriptInterface->ReadStructuredClone(initData, &initDataVal); @@ -241,9 +241,9 @@ return; shared_ptr scriptInterface = gui->GetScriptInterface(); - ScriptInterface::Request rq(*scriptInterface); + ScriptInterface::Request rq(scriptInterface); - JS::RootedObject globalObj(rq.cx, &scriptInterface->GetGlobalObject().toObject()); + JS::RootedObject globalObj(rq.cx, rq.glob); JS::RootedValue funcVal(rq.cx, *callbackFunction); @@ -299,7 +299,7 @@ PROFILE("handleInputBeforeGui"); ScriptInterface::Request rq(*top()->GetScriptInterface()); - JS::RootedValue global(rq.cx, top()->GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); if (top()->GetScriptInterface()->CallFunction(global, "handleInputBeforeGui", handled, *ev, top()->FindObjectUnderMouse())) if (handled) return IN_HANDLED; @@ -315,7 +315,7 @@ { // We can't take the following lines out of this scope because top() may be another gui page than it was when calling handleInputBeforeGui! ScriptInterface::Request rq(*top()->GetScriptInterface()); - JS::RootedValue global(rq.cx, top()->GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); PROFILE("handleInputAfterGui"); if (top()->GetScriptInterface()->CallFunction(global, "handleInputAfterGui", handled, *ev)) Index: ps/trunk/source/gui/ObjectBases/IGUIObject.cpp =================================================================== --- ps/trunk/source/gui/ObjectBases/IGUIObject.cpp +++ ps/trunk/source/gui/ObjectBases/IGUIObject.cpp @@ -300,8 +300,6 @@ void IGUIObject::RegisterScriptHandler(const CStr& eventName, const CStr& Code, CGUI& pGUI) { ScriptInterface::Request rq(pGUI.GetScriptInterface()); - JS::RootedValue globalVal(rq.cx, pGUI.GetGlobalObject()); - JS::RootedObject globalObj(rq.cx, &globalVal.toObject()); const int paramCount = 1; const char* paramNames[paramCount] = { "mouse" }; Index: ps/trunk/source/gui/tests/test_GuiManager.h =================================================================== --- ps/trunk/source/gui/tests/test_GuiManager.h +++ ps/trunk/source/gui/tests/test_GuiManager.h @@ -84,7 +84,7 @@ const ScriptInterface& pageScriptInterface = *(g_GUI->GetActiveGUI()->GetScriptInterface()); ScriptInterface::Request prq(pageScriptInterface); - JS::RootedValue global(prq.cx, pageScriptInterface.GetGlobalObject()); + JS::RootedValue global(prq.cx, prq.globalValue()); // Ensure that our hotkey state was synchronised with the event itself. bool hotkey_pressed_value = false; Index: ps/trunk/source/lobby/XmppClient.cpp =================================================================== --- ps/trunk/source/lobby/XmppClient.cpp +++ ps/trunk/source/lobby/XmppClient.cpp @@ -736,7 +736,7 @@ m_GuiMessageQueue.clear(); // Copy the messages over to the caller script interface. - return scriptInterface.CloneValueFromOtherContext(*m_ScriptInterface, messages); + return scriptInterface.CloneValueFromOtherCompartment(*m_ScriptInterface, messages); } JS::Value XmppClient::GuiPollHistoricMessages(const ScriptInterface& scriptInterface) @@ -754,7 +754,7 @@ m_ScriptInterface->SetPropertyInt(messages, j++, message); // Copy the messages over to the caller script interface. - return scriptInterface.CloneValueFromOtherContext(*m_ScriptInterface, messages); + return scriptInterface.CloneValueFromOtherCompartment(*m_ScriptInterface, messages); } /** Index: ps/trunk/source/network/scripting/JSInterface_Network.cpp =================================================================== --- ps/trunk/source/network/scripting/JSInterface_Network.cpp +++ ps/trunk/source/network/scripting/JSInterface_Network.cpp @@ -158,7 +158,7 @@ ScriptInterface::Request rqNet(g_NetClient->GetScriptInterface()); JS::RootedValue pollNet(rqNet.cx); g_NetClient->GuiPoll(&pollNet); - return pCmptPrivate->pScriptInterface->CloneValueFromOtherContext(g_NetClient->GetScriptInterface(), pollNet); + return pCmptPrivate->pScriptInterface->CloneValueFromOtherCompartment(g_NetClient->GetScriptInterface(), pollNet); } void JSI_Network::SetNetworkGameAttributes(ScriptInterface::CmptPrivate* pCmptPrivate, JS::HandleValue attribs1) Index: ps/trunk/source/ps/Game.cpp =================================================================== --- ps/trunk/source/ps/Game.cpp +++ ps/trunk/source/ps/Game.cpp @@ -325,7 +325,7 @@ shared_ptr scriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface(); ScriptInterface::Request rq(scriptInterface); - JS::RootedValue global(rq.cx, scriptInterface->GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); if (scriptInterface->HasProperty(global, "reallyStartGame")) scriptInterface->CallFunctionVoid(global, "reallyStartGame"); } Index: ps/trunk/source/ps/GameSetup/GameSetup.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/GameSetup.cpp +++ ps/trunk/source/ps/GameSetup/GameSetup.cpp @@ -1615,7 +1615,7 @@ shared_ptr pScriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface(); ScriptInterface::Request rq(pScriptInterface); - JS::RootedValue global(rq.cx, pScriptInterface->GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); LDR_Cancel(); Index: ps/trunk/source/ps/GameSetup/HWDetect.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/HWDetect.cpp +++ ps/trunk/source/ps/GameSetup/HWDetect.cpp @@ -283,7 +283,7 @@ scriptInterface.StringifyJSON(&settings, true)); // Run the detection script: - JS::RootedValue global(rq.cx, scriptInterface.GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); scriptInterface.CallFunctionVoid(global, "RunHardwareDetection", settings); } Index: ps/trunk/source/ps/scripting/JSInterface_Game.cpp =================================================================== --- ps/trunk/source/ps/scripting/JSInterface_Game.cpp +++ ps/trunk/source/ps/scripting/JSInterface_Game.cpp @@ -51,7 +51,7 @@ ScriptInterface::Request rqSim(sim->GetScriptInterface()); JS::RootedValue gameAttribs(rqSim.cx, - sim->GetScriptInterface().CloneValueFromOtherContext(*(pCmptPrivate->pScriptInterface), attribs)); + sim->GetScriptInterface().CloneValueFromOtherCompartment(*(pCmptPrivate->pScriptInterface), attribs)); g_Game->SetPlayerID(playerID); g_Game->StartGame(&gameAttribs, ""); Index: ps/trunk/source/ps/scripting/JSInterface_SavedGame.cpp =================================================================== --- ps/trunk/source/ps/scripting/JSInterface_SavedGame.cpp +++ ps/trunk/source/ps/scripting/JSInterface_SavedGame.cpp @@ -99,7 +99,7 @@ ScriptInterface::Request rqGame(sim->GetScriptInterface()); JS::RootedValue gameContextMetadata(rqGame.cx, - sim->GetScriptInterface().CloneValueFromOtherContext(*(pCmptPrivate->pScriptInterface), guiContextMetadata)); + sim->GetScriptInterface().CloneValueFromOtherCompartment(*(pCmptPrivate->pScriptInterface), guiContextMetadata)); JS::RootedValue gameInitAttributes(rqGame.cx); sim->GetScriptInterface().GetProperty(gameContextMetadata, "initAttributes", &gameInitAttributes); Index: ps/trunk/source/scriptinterface/ScriptInterface.h =================================================================== --- ps/trunk/source/scriptinterface/ScriptInterface.h +++ ps/trunk/source/scriptinterface/ScriptInterface.h @@ -58,7 +58,7 @@ /** - * Abstraction around a SpiderMonkey JSContext. + * Abstraction around a SpiderMonkey JSCompartment. * * Thread-safety: * - May be used in non-main threads. @@ -84,7 +84,7 @@ struct CmptPrivate { - ScriptInterface* pScriptInterface; // the ScriptInterface object the current context belongs to + ScriptInterface* pScriptInterface; // the ScriptInterface object the compartment belongs to void* pCBData; // meant to be used as the "this" object for callback functions } m_CmptPrivate; @@ -95,9 +95,13 @@ shared_ptr GetRuntime() const; /** - * RAII structure which encapsulates an access to the context of a ScriptInterface. - * This struct provides a pointer to the context, and it acts like JSAutoRequest. This - * way, getting the context is safe with respect to the GC. + * RAII structure which encapsulates an access to the context and compartment of a ScriptInterface. + * This struct provides: + * - a pointer to the context, while acting like JSAutoRequest + * - a pointer to the global object of the compartment, while acting like JSAutoCompartment + * + * This way, getting and using those pointers is safe with respect to the GC + * and to the separation of compartments. */ struct Request { @@ -107,15 +111,19 @@ Request(const ScriptInterface& scriptInterface); Request(const ScriptInterface* scriptInterface) : Request(*scriptInterface) {} Request(shared_ptr scriptInterface) : Request(*scriptInterface) {} - Request(const CmptPrivate* CmptPrivate) : Request(CmptPrivate->pScriptInterface) {} + Request(const CmptPrivate* cmptPrivate) : Request(cmptPrivate->pScriptInterface) {} ~Request(); + JS::Value globalValue() const; JSContext* cx; + JSObject* glob; + private: + JSCompartment* m_formerCompartment; }; friend struct Request; /** - * Load global scripts that most script contexts need, + * Load global scripts that most script interfaces need, * located in the /globalscripts directory. VFS must be initialized. */ bool LoadGlobalScripts(); @@ -158,8 +166,6 @@ */ static void CreateArray(const Request& rq, JS::MutableHandleValue objectValue, size_t length = 0); - JS::Value GetGlobalObject() const; - /** * Set the named property on the global object. * Optionally makes it {ReadOnly, DontEnum}. We do not allow to make it DontDelete, so that it can be hotloaded @@ -291,12 +297,12 @@ bool LoadGlobalScriptFile(const VfsPath& path) const; /** - * Construct a new value (usable in this ScriptInterface's context) by cloning - * a value from a different context. + * Construct a new value (usable in this ScriptInterface's compartment) by cloning + * a value from a different compartment. * Complex values (functions, XML, etc) won't be cloned correctly, but basic * types and cyclic references should be fine. */ - JS::Value CloneValueFromOtherContext(const ScriptInterface& otherContext, JS::HandleValue val) const; + JS::Value CloneValueFromOtherCompartment(const ScriptInterface& otherCompartment, JS::HandleValue val) const; /** * Convert a JS::Value to a C++ type. (This might trigger GC.) @@ -327,7 +333,7 @@ /** * Structured clones are a way to serialize 'simple' JS::Values into a buffer - * that can safely be passed between contexts and runtimes and threads. + * that can safely be passed between compartments and between threads. * A StructuredClone can be stored and read multiple times if desired. * We wrap them in shared_ptr so memory management is automatic and * thread-safe. Index: ps/trunk/source/scriptinterface/ScriptInterface.cpp =================================================================== --- ps/trunk/source/scriptinterface/ScriptInterface.cpp +++ ps/trunk/source/scriptinterface/ScriptInterface.cpp @@ -62,24 +62,32 @@ // members have to be called before the runtime destructor. shared_ptr m_runtime; - JS::PersistentRootedObject m_glob; // global scope object - boost::rand48* m_rng; - JS::PersistentRootedObject m_nativeScope; // native function scope object - friend ScriptInterface::Request; private: JSContext* m_cx; - JSCompartment* m_formerCompartment; + JS::PersistentRootedObject m_glob; // global scope object + + public: + boost::rand48* m_rng; + JS::PersistentRootedObject m_nativeScope; // native function scope object }; ScriptInterface::Request::Request(const ScriptInterface& scriptInterface) : cx(scriptInterface.m->m_cx) { JS_BeginRequest(cx); + m_formerCompartment = JS_EnterCompartment(cx, scriptInterface.m->m_glob); + glob = JS::CurrentGlobalOrNull(cx); +} + +JS::Value ScriptInterface::Request::globalValue() const +{ + return JS::ObjectValue(*glob); } ScriptInterface::Request::~Request() { + JS_LeaveCompartment(cx, m_formerCompartment); JS_EndRequest(cx); } @@ -95,46 +103,6 @@ JS_GlobalObjectTraceHook }; -void ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report) -{ - ScriptInterface::Request rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); - - std::stringstream msg; - bool isWarning = JSREPORT_IS_WARNING(report->flags); - msg << (isWarning ? "JavaScript warning: " : "JavaScript error: "); - if (report->filename) - { - msg << report->filename; - msg << " line " << report->lineno << "\n"; - } - - msg << message; - - // If there is an exception, then print its stack trace - JS::RootedValue excn(cx); - if (JS_GetPendingException(cx, &excn) && excn.isObject()) - { - JS::RootedValue stackVal(cx); - JS::RootedObject excnObj(cx, &excn.toObject()); - JS_GetProperty(cx, excnObj, "stack", &stackVal); - - std::string stackText; - ScriptInterface::FromJSVal(rq, stackVal, stackText); - - std::istringstream stream(stackText); - for (std::string line; std::getline(stream, line);) - msg << "\n " << line; - } - - if (isWarning) - LOGWARNING("%s", msg.str().c_str()); - else - LOGERROR("%s", msg.str().c_str()); - - // When running under Valgrind, print more information in the error message -// VALGRIND_PRINTF_BACKTRACE("->"); -} - // Functions in the global namespace: bool print(JSContext* cx, uint argc, JS::Value* vp) @@ -351,70 +319,47 @@ } ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const shared_ptr& runtime) : - m_runtime(runtime), m_glob(runtime->m_rt), m_nativeScope(runtime->m_rt) + m_runtime(runtime), m_cx(runtime->GetGeneralJSContext()), m_glob(runtime->GetJSRuntime()), m_nativeScope(runtime->GetJSRuntime()) { - m_cx = JS_NewContext(m_runtime->m_rt, STACK_CHUNK_SIZE); - ENSURE(m_cx); - - JS_SetOffthreadIonCompilationEnabled(m_runtime->m_rt, true); - - // For GC debugging: - // JS_SetGCZeal(m_cx, 2, JS_DEFAULT_ZEAL_FREQ); - - JS_SetContextPrivate(m_cx, NULL); - - JS_SetErrorReporter(m_runtime->m_rt, ErrorReporter); - - JS_SetGlobalJitCompilerOption(m_runtime->m_rt, JSJITCOMPILER_ION_ENABLE, 1); - JS_SetGlobalJitCompilerOption(m_runtime->m_rt, JSJITCOMPILER_BASELINE_ENABLE, 1); - - JS::RuntimeOptionsRef(m_cx) - .setExtraWarnings(true) - .setWerror(false) - .setStrictMode(true); - JS::CompartmentOptions opt; opt.setVersion(JSVERSION_LATEST); // Keep JIT code during non-shrinking GCs. This brings a quite big performance improvement. opt.setPreserveJitCode(true); JSAutoRequest rq(m_cx); - JS::RootedObject globalRootedVal(m_cx, JS_NewGlobalObject(m_cx, &global_class, NULL, JS::OnNewGlobalHookOption::FireOnNewGlobalHook, opt)); - m_formerCompartment = JS_EnterCompartment(m_cx, globalRootedVal); - ENSURE(JS_InitStandardClasses(m_cx, globalRootedVal)); - m_glob = globalRootedVal.get(); + m_glob = JS_NewGlobalObject(m_cx, &global_class, nullptr, JS::OnNewGlobalHookOption::FireOnNewGlobalHook, opt); - JS_DefineProperty(m_cx, m_glob, "global", globalRootedVal, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); + JSAutoCompartment autoCmpt(m_cx, m_glob); + + ENSURE(JS_InitStandardClasses(m_cx, m_glob)); + + JS_DefineProperty(m_cx, m_glob, "global", m_glob, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); m_nativeScope = JS_DefineObject(m_cx, m_glob, nativeScopeName, nullptr, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineFunction(m_cx, globalRootedVal, "print", ::print, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineFunction(m_cx, globalRootedVal, "log", ::logmsg, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineFunction(m_cx, globalRootedVal, "warn", ::warn, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineFunction(m_cx, globalRootedVal, "error", ::error, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineFunction(m_cx, globalRootedVal, "clone", ::deepcopy, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); - JS_DefineFunction(m_cx, globalRootedVal, "deepfreeze", ::deepfreeze, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); + JS_DefineFunction(m_cx, m_glob, "print", ::print, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); + JS_DefineFunction(m_cx, m_glob, "log", ::logmsg, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); + JS_DefineFunction(m_cx, m_glob, "warn", ::warn, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); + JS_DefineFunction(m_cx, m_glob, "error", ::error, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); + JS_DefineFunction(m_cx, m_glob, "clone", ::deepcopy, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); + JS_DefineFunction(m_cx, m_glob, "deepfreeze", ::deepfreeze, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); Register("ProfileStart", ::ProfileStart, 1); Register("ProfileStop", ::ProfileStop, 0); Register("ProfileAttribute", ::ProfileAttribute, 1); - runtime->RegisterContext(m_cx); + m_runtime->RegisterCompartment(js::GetObjectCompartment(m_glob)); } ScriptInterface_impl::~ScriptInterface_impl() { - m_runtime->UnRegisterContext(m_cx); - { - JSAutoRequest rq(m_cx); - JS_LeaveCompartment(m_cx, m_formerCompartment); - } - JS_DestroyContext(m_cx); + m_runtime->UnRegisterCompartment(js::GetObjectCompartment(m_glob)); } void ScriptInterface_impl::Register(const char* name, JSNative fptr, uint nargs) const { JSAutoRequest rq(m_cx); + JSAutoCompartment autoCmpt(m_cx, m_glob); JS::RootedObject nativeScope(m_cx, m_nativeScope); JS::RootedFunction func(m_cx, JS_DefineFunction(m_cx, nativeScope, name, fptr, nargs, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)); } @@ -431,7 +376,7 @@ Request rq(this); m_CmptPrivate.pScriptInterface = this; - JS_SetContextPrivate(rq.cx, (void*)&m_CmptPrivate); + JS_SetCompartmentPrivate(js::GetObjectCompartment(rq.glob), (void*)&m_CmptPrivate); } ScriptInterface::~ScriptInterface() @@ -450,7 +395,7 @@ ScriptInterface::CmptPrivate* ScriptInterface::GetScriptInterfaceAndCBData(JSContext* cx) { - CmptPrivate* pCmptPrivate = (CmptPrivate*)JS_GetContextPrivate(cx); + CmptPrivate* pCmptPrivate = (CmptPrivate*)JS_GetCompartmentPrivate(js::GetContextCompartment(cx)); return pCmptPrivate; } @@ -478,7 +423,7 @@ { Request rq(this); JS::RootedValue math(rq.cx); - JS::RootedObject global(rq.cx, m->m_glob); + JS::RootedObject global(rq.cx, rq.glob); if (JS_GetProperty(rq.cx, global, "Math", &math) && math.isObject()) { JS::RootedObject mathObj(rq.cx, &math.toObject()); @@ -502,7 +447,7 @@ JSRuntime* ScriptInterface::GetJSRuntime() const { - return m->m_runtime->m_rt; + return m->m_runtime->GetJSRuntime(); } shared_ptr ScriptInterface::GetRuntime() const @@ -535,7 +480,7 @@ throw PSERROR_Scripting_DefineType_AlreadyExists(); } - JS::RootedObject global(rq.cx, m->m_glob); + JS::RootedObject global(rq.cx, rq.glob); JS::RootedObject obj(rq.cx, JS_InitClass(rq.cx, global, nullptr, clasp, constructor, minArgs, // Constructor, min args @@ -597,16 +542,10 @@ throw PSERROR_Scripting_CreateObjectFailed(); } -JS::Value ScriptInterface::GetGlobalObject() const -{ - Request rq(this); - return JS::ObjectValue(*JS::CurrentGlobalOrNull(rq.cx)); -} - bool ScriptInterface::SetGlobal_(const char* name, JS::HandleValue value, bool replace, bool constant, bool enumerate) { Request rq(this); - JS::RootedObject global(rq.cx, m->m_glob); + JS::RootedObject global(rq.cx, rq.glob); bool found; if (!JS_HasProperty(rq.cx, global, name, &found)) @@ -834,7 +773,7 @@ bool ScriptInterface::LoadScript(const VfsPath& filename, const std::string& code) const { Request rq(this); - JS::RootedObject global(rq.cx, m->m_glob); + JS::RootedObject global(rq.cx, rq.glob); utf16string codeUtf16(code.begin(), code.end()); uint lineNo = 1; // CompileOptions does not copy the contents of the filename string pointer. @@ -1036,12 +975,12 @@ JS::RootedValue indentVal(rq.cx, JS::Int32Value(2)); // Temporary disable the error reporter, so we don't print complaints about cyclic values - JSErrorReporter er = JS_SetErrorReporter(m->m_runtime->m_rt, NULL); + JSErrorReporter er = JS_SetErrorReporter(GetJSRuntime(), nullptr); bool ok = JS_Stringify(rq.cx, obj, nullptr, indentVal, &Stringifier::callback, &str); // Restore error reporter - JS_SetErrorReporter(m->m_runtime->m_rt, er); + JS_SetErrorReporter(GetJSRuntime(), er); if (ok) return str.stream.str(); @@ -1077,12 +1016,12 @@ return JS_IsExceptionPending(rq.cx) ? true : false; } -JS::Value ScriptInterface::CloneValueFromOtherContext(const ScriptInterface& otherContext, JS::HandleValue val) const +JS::Value ScriptInterface::CloneValueFromOtherCompartment(const ScriptInterface& otherCompartment, JS::HandleValue val) const { - PROFILE("CloneValueFromOtherContext"); + PROFILE("CloneValueFromOtherCompartment"); Request rq(this); JS::RootedValue out(rq.cx); - shared_ptr structuredClone = otherContext.WriteStructuredClone(val); + shared_ptr structuredClone = otherCompartment.WriteStructuredClone(val); ReadStructuredClone(structuredClone, &out); return out.get(); } Index: ps/trunk/source/scriptinterface/ScriptRuntime.h =================================================================== --- ps/trunk/source/scriptinterface/ScriptRuntime.h +++ ps/trunk/source/scriptinterface/ScriptRuntime.h @@ -30,10 +30,10 @@ constexpr int DEFAULT_HEAP_GROWTH_BYTES_GCTRIGGER = 2 * 1024 * 1024; /** - * Abstraction around a SpiderMonkey JSRuntime. - * Each ScriptRuntime can be used to initialize several ScriptInterface - * contexts which can then share data, but a single ScriptRuntime should - * only be used on a single thread. + * Abstraction around a SpiderMonkey JSRuntime/JSContext. + * + * A single ScriptRuntime, with the associated runtime and context, + * should only be used on a single thread. * * (One means to share data between threads and runtimes is to create * a ScriptInterface::StructuredClone.) @@ -46,9 +46,7 @@ ~ScriptRuntime(); /** - * Returns a runtime, which can used to initialise any number of - * ScriptInterfaces contexts. Values created in one context may be used - * in any other context from the same runtime (but not any other runtime). + * Returns a runtime/context, in which any number of ScriptInterfaces compartments can live. * Each runtime should only ever be used on a single thread. * @param runtimeSize Maximum size in bytes of the new runtime * @param heapGrowthBytesGCTrigger Size in bytes of cumulated allocations after which a GC will be triggered @@ -70,16 +68,30 @@ void MaybeIncrementalGC(double delay); void ShrinkingGC(); - void RegisterContext(JSContext* cx); - void UnRegisterContext(JSContext* cx); + /** + * This is used to keep track of compartments which should be prepared for a GC. + */ + void RegisterCompartment(JSCompartment* cmpt); + void UnRegisterCompartment(JSCompartment* cmpt); - JSRuntime* m_rt; + JSRuntime* GetJSRuntime() const { return m_rt; } + + /** + * GetGeneralJSContext returns the context without starting a GC request and without + * entering any compartment. It should only be used in specific situations, such as + * creating a new compartment, or as an unsafe alternative to GetJSRuntime. + * If you need the compartmented context of a ScriptInterface, you should create a + * ScriptInterface::Request and use the context from that. + */ + JSContext* GetGeneralJSContext() const { return m_cx; } private: - void PrepareContextsForIncrementalGC(); + JSRuntime* m_rt; + JSContext* m_cx; - std::list m_Contexts; + void PrepareCompartmentsForIncrementalGC() const; + std::list m_Compartments; int m_RuntimeSize; int m_HeapGrowthBytesGCTrigger; Index: ps/trunk/source/scriptinterface/ScriptRuntime.cpp =================================================================== --- ps/trunk/source/scriptinterface/ScriptRuntime.cpp +++ ps/trunk/source/scriptinterface/ScriptRuntime.cpp @@ -22,6 +22,7 @@ #include "ps/GameSetup/Config.h" #include "ps/Profile.h" #include "scriptinterface/ScriptEngine.h" +#include "scriptinterface/ScriptInterface.h" void GCSliceCallbackHook(JSRuntime* UNUSED(rt), JS::GCProgress progress, const JS::GCDescription& UNUSED(desc)) @@ -89,6 +90,52 @@ #endif } + +namespace { + +void ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report) +{ + ScriptInterface::Request rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); + + std::stringstream msg; + bool isWarning = JSREPORT_IS_WARNING(report->flags); + msg << (isWarning ? "JavaScript warning: " : "JavaScript error: "); + if (report->filename) + { + msg << report->filename; + msg << " line " << report->lineno << "\n"; + } + + msg << message; + + // If there is an exception, then print its stack trace + JS::RootedValue excn(rq.cx); + if (JS_GetPendingException(rq.cx, &excn) && excn.isObject()) + { + JS::RootedValue stackVal(rq.cx); + JS::RootedObject excnObj(rq.cx, &excn.toObject()); + JS_GetProperty(rq.cx, excnObj, "stack", &stackVal); + + std::string stackText; + ScriptInterface::FromJSVal(rq, stackVal, stackText); + + std::istringstream stream(stackText); + for (std::string line; std::getline(stream, line);) + msg << "\n " << line; + } + + if (isWarning) + LOGWARNING("%s", msg.str().c_str()); + else + LOGERROR("%s", msg.str().c_str()); + + // When running under Valgrind, print more information in the error message + // VALGRIND_PRINTF_BACKTRACE("->"); +} + +} // anonymous namespace + + shared_ptr ScriptRuntime::CreateRuntime(int runtimeSize, int heapGrowthBytesGCTrigger) { return shared_ptr(new ScriptRuntime(runtimeSize, heapGrowthBytesGCTrigger)); @@ -116,24 +163,47 @@ JS_SetGCParameter(m_rt, JSGC_DYNAMIC_HEAP_GROWTH, false); ScriptEngine::GetSingleton().RegisterRuntime(m_rt); + + + m_cx = JS_NewContext(m_rt, STACK_CHUNK_SIZE); + ENSURE(m_cx); // TODO: error handling + + JS_SetOffthreadIonCompilationEnabled(m_rt, true); + + // For GC debugging: + // JS_SetGCZeal(m_cx, 2, JS_DEFAULT_ZEAL_FREQ); + + JS_SetContextPrivate(m_cx, nullptr); + + JS_SetErrorReporter(m_rt, ErrorReporter); + + JS_SetGlobalJitCompilerOption(m_rt, JSJITCOMPILER_ION_ENABLE, 1); + JS_SetGlobalJitCompilerOption(m_rt, JSJITCOMPILER_BASELINE_ENABLE, 1); + + JS::RuntimeOptionsRef(m_cx) + .setExtraWarnings(true) + .setWerror(false) + .setStrictMode(true); } ScriptRuntime::~ScriptRuntime() { + JS_DestroyContext(m_cx); JS_DestroyRuntime(m_rt); ENSURE(ScriptEngine::IsInitialised() && "The ScriptEngine must be active (initialized and not yet shut down) when destroying a ScriptRuntime!"); ScriptEngine::GetSingleton().UnRegisterRuntime(m_rt); } -void ScriptRuntime::RegisterContext(JSContext* cx) +void ScriptRuntime::RegisterCompartment(JSCompartment* cmpt) { - m_Contexts.push_back(cx); + ENSURE(cmpt); + m_Compartments.push_back(cmpt); } -void ScriptRuntime::UnRegisterContext(JSContext* cx) +void ScriptRuntime::UnRegisterCompartment(JSCompartment* cmpt) { - m_Contexts.remove(cx); + m_Compartments.remove(cmpt); } #define GC_DEBUG_PRINT 0 @@ -199,7 +269,7 @@ #if GC_DEBUG_PRINT printf("Finishing incremental GC because gcBytes > m_RuntimeSize / 2. \n"); #endif - PrepareContextsForIncrementalGC(); + PrepareCompartmentsForIncrementalGC(); JS::FinishIncrementalGC(m_rt, JS::gcreason::REFRESH_FRAME); } else @@ -228,7 +298,7 @@ else printf("Running incremental GC slice \n"); #endif - PrepareContextsForIncrementalGC(); + PrepareCompartmentsForIncrementalGC(); if (!JS::IsIncrementalGCInProgress(m_rt)) JS::StartIncrementalGC(m_rt, GC_NORMAL, JS::gcreason::REFRESH_FRAME, GCSliceTimeBudget); else @@ -247,8 +317,8 @@ JS_SetGCParameter(m_rt, JSGC_MODE, JSGC_MODE_INCREMENTAL); } -void ScriptRuntime::PrepareContextsForIncrementalGC() +void ScriptRuntime::PrepareCompartmentsForIncrementalGC() const { - for (JSContext* const& ctx : m_Contexts) - JS::PrepareZoneForGC(js::GetCompartmentZone(js::GetContextCompartment(ctx))); + for (JSCompartment* const& cmpt : m_Compartments) + JS::PrepareZoneForGC(js::GetCompartmentZone(cmpt)); } Index: ps/trunk/source/scriptinterface/tests/test_ScriptConversions.h =================================================================== --- ps/trunk/source/scriptinterface/tests/test_ScriptConversions.h +++ ps/trunk/source/scriptinterface/tests/test_ScriptConversions.h @@ -43,7 +43,7 @@ // We want to convert values to strings, but can't just call toSource() on them // since they might not be objects. So just use uneval. std::string source; - JS::RootedValue global(rq.cx, script.GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); TS_ASSERT(script.CallFunction(global, "uneval", source, v1)); TS_ASSERT_STR_EQUALS(source, expected); @@ -60,7 +60,7 @@ ScriptInterface::ToJSVal(rq, &v1, value); std::string source; - JS::RootedValue global(rq.cx, script.GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); TS_ASSERT(script.CallFunction(global, "uneval", source, v1)); if (expected) @@ -90,7 +90,7 @@ ScriptInterface::ToJSVal(rq, &r1, r); std::string source; - JS::RootedValue global(rq.cx, script.GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); TS_ASSERT(script.CallFunction(global, "uneval", source, r1)); TS_ASSERT_STR_EQUALS(source, expected); Index: ps/trunk/source/scriptinterface/tests/test_ScriptInterface.h =================================================================== --- ps/trunk/source/scriptinterface/tests/test_ScriptInterface.h +++ ps/trunk/source/scriptinterface/tests/test_ScriptInterface.h @@ -72,7 +72,7 @@ { ScriptInterface::Request rq2(script2); - JS::RootedValue obj2(rq2.cx, script2.CloneValueFromOtherContext(script1, obj1)); + JS::RootedValue obj2(rq2.cx, script2.CloneValueFromOtherCompartment(script1, obj1)); std::string source; TS_ASSERT(script2.CallFunction(obj2, "toSource", source)); @@ -94,7 +94,7 @@ { ScriptInterface::Request rq2(script2); - JS::RootedValue obj2(rq2.cx, script2.CloneValueFromOtherContext(script1, obj1)); + JS::RootedValue obj2(rq2.cx, script2.CloneValueFromOtherCompartment(script1, obj1)); std::string source; TS_ASSERT(script2.CallFunction(obj2, "toSource", source)); @@ -114,7 +114,7 @@ { ScriptInterface::Request rq2(script2); - JS::RootedValue obj2(rq2.cx, script2.CloneValueFromOtherContext(script1, obj1)); + JS::RootedValue obj2(rq2.cx, script2.CloneValueFromOtherCompartment(script1, obj1)); // Use JSAPI function to check if the values of the properties "a", "b" are equals a.x[0] JS::RootedValue prop_a(rq2.cx); Index: ps/trunk/source/simulation2/Simulation2.cpp =================================================================== --- ps/trunk/source/simulation2/Simulation2.cpp +++ ps/trunk/source/simulation2/Simulation2.cpp @@ -53,7 +53,7 @@ m_SimContext(), m_ComponentManager(m_SimContext, rt), m_EnableOOSLog(false), m_EnableSerializationTest(false), m_RejoinTestTurn(-1), m_TestingRejoin(false), m_SecondaryTerrain(nullptr), m_SecondaryContext(nullptr), m_SecondaryComponentManager(nullptr), m_SecondaryLoadedScripts(nullptr), - m_MapSettings(rt->m_rt), m_InitAttributes(rt->m_rt) + m_MapSettings(rt->GetJSRuntime()), m_InitAttributes(rt->GetJSRuntime()) { m_SimContext.m_UnitManager = unitManager; m_SimContext.m_Terrain = terrain; @@ -173,7 +173,7 @@ ScriptInterface::Request rqNew(newScript); for (const SimulationCommand& command : commands) { - JS::RootedValue tmpCommand(rqNew.cx, newScript.CloneValueFromOtherContext(oldScript, command.data)); + JS::RootedValue tmpCommand(rqNew.cx, newScript.CloneValueFromOtherCompartment(oldScript, command.data)); newScript.FreezeObject(tmpCommand, true); SimulationCommand cmd(command.player, rqNew.cx, tmpCommand); newCommands.emplace_back(std::move(cmd)); @@ -423,7 +423,7 @@ { ScriptInterface::Request rq2(m_SecondaryComponentManager->GetScriptInterface()); JS::RootedValue mapSettingsCloned(rq2.cx, - m_SecondaryComponentManager->GetScriptInterface().CloneValueFromOtherContext( + m_SecondaryComponentManager->GetScriptInterface().CloneValueFromOtherCompartment( scriptInterface, m_MapSettings)); ENSURE(LoadTriggerScripts(*m_SecondaryComponentManager, mapSettingsCloned, m_SecondaryLoadedScripts)); } @@ -734,14 +734,14 @@ void CSimulation2::PreInitGame() { ScriptInterface::Request rq(GetScriptInterface()); - JS::RootedValue global(rq.cx, GetScriptInterface().GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); GetScriptInterface().CallFunctionVoid(global, "PreInitGame"); } void CSimulation2::InitGame() { ScriptInterface::Request rq(GetScriptInterface()); - JS::RootedValue global(rq.cx, GetScriptInterface().GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); JS::RootedValue settings(rq.cx); JS::RootedValue tmpInitAttributes(rq.cx, GetInitAttributes()); @@ -840,7 +840,7 @@ void CSimulation2::LoadPlayerSettings(bool newPlayers) { ScriptInterface::Request rq(GetScriptInterface()); - JS::RootedValue global(rq.cx, GetScriptInterface().GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); GetScriptInterface().CallFunctionVoid(global, "LoadPlayerSettings", m->m_MapSettings, newPlayers); } @@ -848,7 +848,7 @@ { ScriptInterface::Request rq(GetScriptInterface()); - JS::RootedValue global(rq.cx, GetScriptInterface().GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); // Initialize here instead of in Update() GetScriptInterface().CallFunctionVoid(global, "LoadMapSettings", m->m_MapSettings); Index: ps/trunk/source/simulation2/components/CCmpAIManager.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpAIManager.cpp +++ ps/trunk/source/simulation2/components/CCmpAIManager.cpp @@ -111,7 +111,7 @@ std::string moduleName; std::string constructor; JS::RootedValue objectWithConstructor(rq.cx); // object that should contain the constructor function - JS::RootedValue global(rq.cx, m_ScriptInterface->GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); JS::RootedValue ctor(rq.cx); if (!m_ScriptInterface->HasProperty(metadata, "moduleName")) { @@ -216,10 +216,10 @@ m_CommandsComputed(true), m_HasLoadedEntityTemplates(false), m_HasSharedComponent(false), - m_EntityTemplates(g_ScriptRuntime->m_rt), - m_SharedAIObj(g_ScriptRuntime->m_rt), - m_PassabilityMapVal(g_ScriptRuntime->m_rt), - m_TerritoryMapVal(g_ScriptRuntime->m_rt) + m_EntityTemplates(g_ScriptRuntime->GetJSRuntime()), + m_SharedAIObj(g_ScriptRuntime->GetJSRuntime()), + m_PassabilityMapVal(g_ScriptRuntime->GetJSRuntime()), + m_TerritoryMapVal(g_ScriptRuntime->GetJSRuntime()) { m_ScriptInterface->ReplaceNondeterministicRNG(m_RNG); @@ -422,7 +422,7 @@ // Constructor name is SharedScript, it's in the module API3 // TODO: Hardcoding this is bad, we need a smarter way. JS::RootedValue AIModule(rq.cx); - JS::RootedValue global(rq.cx, m_ScriptInterface->GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); JS::RootedValue ctor(rq.cx); if (!m_ScriptInterface->GetProperty(global, "API3", &AIModule) || AIModule.isUndefined()) { Index: ps/trunk/source/simulation2/components/CCmpCommandQueue.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpCommandQueue.cpp +++ ps/trunk/source/simulation2/components/CCmpCommandQueue.cpp @@ -103,7 +103,7 @@ const ScriptInterface& scriptInterface = GetSimContext().GetScriptInterface(); ScriptInterface::Request rq(scriptInterface); - JS::RootedValue global(rq.cx, scriptInterface.GetGlobalObject()); + JS::RootedValue global(rq.cx, rq.globalValue()); std::vector localCommands; m_LocalQueue.swap(localCommands); Index: ps/trunk/source/simulation2/scripting/EngineScriptConversions.cpp =================================================================== --- ps/trunk/source/simulation2/scripting/EngineScriptConversions.cpp +++ ps/trunk/source/simulation2/scripting/EngineScriptConversions.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -158,8 +158,7 @@ template<> void ScriptInterface::ToJSVal(const Request& rq, JS::MutableHandleValue ret, const CFixedVector3D& val) { - ScriptInterface::CmptPrivate* pCmptPrivate = ScriptInterface::GetScriptInterfaceAndCBData(rq.cx); - JS::RootedObject global(rq.cx, &pCmptPrivate->pScriptInterface->GetGlobalObject().toObject()); + JS::RootedObject global(rq.cx, rq.glob); JS::RootedValue valueVector3D(rq.cx); if (!JS_GetProperty(rq.cx, global, "Vector3D", &valueVector3D)) FAIL_VOID("Failed to get Vector3D constructor"); @@ -192,8 +191,7 @@ template<> void ScriptInterface::ToJSVal(const Request& rq, JS::MutableHandleValue ret, const CFixedVector2D& val) { - ScriptInterface::CmptPrivate* pCmptPrivate = ScriptInterface::GetScriptInterfaceAndCBData(rq.cx); - JS::RootedObject global(rq.cx, &pCmptPrivate->pScriptInterface->GetGlobalObject().toObject()); + JS::RootedObject global(rq.cx, rq.glob); JS::RootedValue valueVector2D(rq.cx); if (!JS_GetProperty(rq.cx, global, "Vector2D", &valueVector2D)) FAIL_VOID("Failed to get Vector2D constructor"); Index: ps/trunk/source/simulation2/scripting/JSInterface_Simulation.cpp =================================================================== --- ps/trunk/source/simulation2/scripting/JSInterface_Simulation.cpp +++ ps/trunk/source/simulation2/scripting/JSInterface_Simulation.cpp @@ -51,11 +51,11 @@ return JS::UndefinedValue(); ScriptInterface::Request rqSim(sim->GetScriptInterface()); - JS::RootedValue arg(rqSim.cx, sim->GetScriptInterface().CloneValueFromOtherContext(*(pCmptPrivate->pScriptInterface), data)); + JS::RootedValue arg(rqSim.cx, sim->GetScriptInterface().CloneValueFromOtherCompartment(*(pCmptPrivate->pScriptInterface), data)); JS::RootedValue ret(rqSim.cx); cmpGuiInterface->ScriptCall(g_Game->GetViewedPlayerID(), name, arg, &ret); - return pCmptPrivate->pScriptInterface->CloneValueFromOtherContext(sim->GetScriptInterface(), ret); + return pCmptPrivate->pScriptInterface->CloneValueFromOtherCompartment(sim->GetScriptInterface(), ret); } void JSI_Simulation::PostNetworkCommand(ScriptInterface::CmptPrivate* pCmptPrivate, JS::HandleValue cmd) @@ -71,7 +71,7 @@ return; ScriptInterface::Request rqSim(sim->GetScriptInterface()); - JS::RootedValue cmd2(rqSim.cx, sim->GetScriptInterface().CloneValueFromOtherContext(*(pCmptPrivate->pScriptInterface), cmd)); + JS::RootedValue cmd2(rqSim.cx, sim->GetScriptInterface().CloneValueFromOtherCompartment(*(pCmptPrivate->pScriptInterface), cmd)); cmpCommandQueue->PostNetworkCommand(cmd2); }