Index: source/scriptinterface/ScriptConversions.h =================================================================== --- source/scriptinterface/ScriptConversions.h +++ source/scriptinterface/ScriptConversions.h @@ -22,6 +22,8 @@ #include "scriptinterface/ScriptExtraHeaders.h" // for typed arrays #include +#include +#include template static void ToJSVal_vector(JSContext* cx, JS::MutableHandleValue ret, const std::vector& val) { @@ -77,6 +79,7 @@ #undef FAIL +// This is a workaround not being able to partially specialize functions. #define JSVAL_VECTOR(T) \ template<> void ScriptInterface::ToJSVal >(JSContext* cx, JS::MutableHandleValue ret, const std::vector& val) \ { \ @@ -87,6 +90,64 @@ return FromJSVal_vector(cx, v, out); \ } +template +static void ToJSVal_unordered_map(JSContext* cx, JS::MutableHandleValue ret, const std::unordered_map& val) +{ + JSAutoRequest rq(cx); + JS::RootedObject obj(cx, JS_NewPlainObject(cx)); + if (!obj) + { + ret.setUndefined(); + return; + } + for (const std::pair& item : val) + { + JS::RootedValue el(cx); + ScriptInterface::ToJSVal(cx, &el, item.second); + JS_SetProperty(cx, obj, item.first.c_str(), el); + } + ret.setObject(*obj); +} + +#define FAIL(msg) STMT(JS_ReportError(cx, msg); return false) + +template +static bool FromJSVal_unordered_map(JSContext* cx, JS::HandleValue v, std::unordered_map& out) +{ + ScriptInterface* scriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface; + JSAutoRequest rq(cx); + + ENSURE(out.empty()); + + JS::RootedObject obj(cx); + if (!v.isObject()) + FAIL("Argument must be an object."); + + std::vector props; + scriptInterface->EnumeratePropertyNames(v, true, props); + for (const std::string& prop : props) + { + U val; + if (!ScriptInterface::FromJSProperty(cx, v, prop.c_str(), val)) + FAIL("Failed to read object property."); + out.emplace(prop, val); + } + return true; +} + +#undef FAIL + +// This is a workaround not being able to partially specialize functions. +#define JSVAL_MAP(T, U) \ +template<> void ScriptInterface::ToJSVal>(JSContext* cx, JS::MutableHandleValue ret, const std::unordered_map& val) \ +{ \ + ToJSVal_unordered_map(cx, ret, val); \ +} \ +template<> bool ScriptInterface::FromJSVal>(JSContext* cx, JS::HandleValue v, std::unordered_map& out) \ +{ \ + return FromJSVal_unordered_map(cx, v, out); \ +} + template bool ScriptInterface::FromJSProperty(JSContext* cx, const JS::HandleValue val, const char* name, T& ret, bool strict) { if (!val.isObject()) Index: source/scriptinterface/tests/test_ScriptConversions.h =================================================================== --- source/scriptinterface/tests/test_ScriptConversions.h +++ source/scriptinterface/tests/test_ScriptConversions.h @@ -17,6 +17,7 @@ #include "lib/self_test.h" +#include "scriptinterface/ScriptConversions.h" #include "scriptinterface/ScriptInterface.h" #include "maths/Fixed.h" @@ -28,6 +29,8 @@ #include "jsapi.h" +JSVAL_MAP(std::string, int) + class TestScriptConversions : public CxxTest::TestSuite { template @@ -275,4 +278,28 @@ TS_ASSERT(ScriptInterface::FromJSVal(cx, v2, test_out_utf8)); TS_ASSERT_EQUALS(test_out_utf8, in_utf8); } + + void test_containers() + { + ScriptInterface script("Test", "Test", g_ScriptRuntime); + JSContext* cx = script.GetContext(); + + std::vector vec_in = { L"test", L"test_2" }; + std::vector vec_val = { "test", "test_2" }; + std::vector vec_out; + + JS::RootedValue vec_js(cx); + ScriptInterface::ToJSVal(cx, &vec_js, vec_in); + TS_ASSERT(ScriptInterface::FromJSVal(cx, vec_js, vec_out)); + TS_ASSERT_EQUALS(vec_out, vec_val); + + std::unordered_map map_in = { { "test", 5 }, { "test_2", -3 } }; + std::unordered_map map_val = { { "test", 5 }, { "test_2", -3 } }; + std::unordered_map map_out; + + JS::RootedValue map_js(cx); + ScriptInterface::ToJSVal(cx, &map_js, map_in); + TS_ASSERT(ScriptInterface::FromJSVal(cx, map_js, map_out)); + TS_ASSERT_EQUALS(map_out, map_val); + } };