diff --git a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt
index c96abc47e3..250b599fbf 100644
--- a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt
+++ b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt
@@ -2522,9 +2522,14 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_javascript/choc/text/choc_StringUtilities.h"
"../../../../../modules/juce_javascript/choc/text/choc_UTF8.h"
"../../../../../modules/juce_javascript/choc/LICENSE.md"
- "../../../../../modules/juce_javascript/javascript/juce_Javascript.cpp"
- "../../../../../modules/juce_javascript/javascript/juce_Javascript.h"
+ "../../../../../modules/juce_javascript/detail/juce_QuickJSHelpers.h"
"../../../../../modules/juce_javascript/javascript/juce_Javascript_test.cpp"
+ "../../../../../modules/juce_javascript/javascript/juce_JavascriptEngine.cpp"
+ "../../../../../modules/juce_javascript/javascript/juce_JavascriptEngine.h"
+ "../../../../../modules/juce_javascript/javascript/juce_JSCursor.cpp"
+ "../../../../../modules/juce_javascript/javascript/juce_JSCursor.h"
+ "../../../../../modules/juce_javascript/javascript/juce_JSObject.cpp"
+ "../../../../../modules/juce_javascript/javascript/juce_JSObject.h"
"../../../../../modules/juce_javascript/juce_javascript.cpp"
"../../../../../modules/juce_javascript/juce_javascript.h"
"../../../../../modules/juce_opengl/geometry/juce_Draggable3DOrientation.h"
@@ -5112,9 +5117,14 @@ set_source_files_properties(
"../../../../../modules/juce_javascript/choc/text/choc_StringUtilities.h"
"../../../../../modules/juce_javascript/choc/text/choc_UTF8.h"
"../../../../../modules/juce_javascript/choc/LICENSE.md"
- "../../../../../modules/juce_javascript/javascript/juce_Javascript.cpp"
- "../../../../../modules/juce_javascript/javascript/juce_Javascript.h"
+ "../../../../../modules/juce_javascript/detail/juce_QuickJSHelpers.h"
"../../../../../modules/juce_javascript/javascript/juce_Javascript_test.cpp"
+ "../../../../../modules/juce_javascript/javascript/juce_JavascriptEngine.cpp"
+ "../../../../../modules/juce_javascript/javascript/juce_JavascriptEngine.h"
+ "../../../../../modules/juce_javascript/javascript/juce_JSCursor.cpp"
+ "../../../../../modules/juce_javascript/javascript/juce_JSCursor.h"
+ "../../../../../modules/juce_javascript/javascript/juce_JSObject.cpp"
+ "../../../../../modules/juce_javascript/javascript/juce_JSObject.h"
"../../../../../modules/juce_javascript/juce_javascript.cpp"
"../../../../../modules/juce_javascript/juce_javascript.h"
"../../../../../modules/juce_opengl/geometry/juce_Draggable3DOrientation.h"
diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj
index 2b47e96c47..c8cb331214 100644
--- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj
+++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj
@@ -3092,10 +3092,16 @@
true
-
+
true
-
+
+ true
+
+
+ true
+
+
true
@@ -4644,7 +4650,10 @@
-
+
+
+
+
diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters
index e250cae838..6f25f4f2bb 100644
--- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters
+++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters
@@ -764,6 +764,9 @@
{D2777265-A166-EDB8-938A-47D1EDAADA42}
+
+ {D98D147D-FDDB-12D7-2C59-B686F458CE18}
+
{AF145034-7775-F013-E219-381BA369C108}
@@ -3934,10 +3937,16 @@
JUCE Modules\juce_gui_extra
-
+
JUCE Modules\juce_javascript\javascript
-
+
+ JUCE Modules\juce_javascript\javascript
+
+
+ JUCE Modules\juce_javascript\javascript
+
+
JUCE Modules\juce_javascript\javascript
@@ -8367,7 +8376,16 @@
JUCE Modules\juce_javascript\choc\text
-
+
+ JUCE Modules\juce_javascript\detail
+
+
+ JUCE Modules\juce_javascript\javascript
+
+
+ JUCE Modules\juce_javascript\javascript
+
+
JUCE Modules\juce_javascript\javascript
diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj
index 2e430ce67a..6ec9f04811 100644
--- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj
+++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj
@@ -3092,10 +3092,16 @@
true
-
+
true
-
+
+ true
+
+
+ true
+
+
true
@@ -4644,7 +4650,10 @@
-
+
+
+
+
diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters
index ed754a8979..730cd91e51 100644
--- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters
+++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters
@@ -764,6 +764,9 @@
{D2777265-A166-EDB8-938A-47D1EDAADA42}
+
+ {D98D147D-FDDB-12D7-2C59-B686F458CE18}
+
{AF145034-7775-F013-E219-381BA369C108}
@@ -3934,10 +3937,16 @@
JUCE Modules\juce_gui_extra
-
+
JUCE Modules\juce_javascript\javascript
-
+
+ JUCE Modules\juce_javascript\javascript
+
+
+ JUCE Modules\juce_javascript\javascript
+
+
JUCE Modules\juce_javascript\javascript
@@ -8367,7 +8376,16 @@
JUCE Modules\juce_javascript\choc\text
-
+
+ JUCE Modules\juce_javascript\detail
+
+
+ JUCE Modules\juce_javascript\javascript
+
+
+ JUCE Modules\juce_javascript\javascript
+
+
JUCE Modules\juce_javascript\javascript
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj
index f6ad6633a7..3f13a4a84c 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj
+++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj
@@ -2927,10 +2927,16 @@
true
-
+
true
-
+
+ true
+
+
+ true
+
+
true
@@ -4444,7 +4450,10 @@
-
+
+
+
+
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters
index c09875552a..399197e763 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters
+++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters
@@ -716,6 +716,9 @@
{D2777265-A166-EDB8-938A-47D1EDAADA42}
+
+ {D98D147D-FDDB-12D7-2C59-B686F458CE18}
+
{AF145034-7775-F013-E219-381BA369C108}
@@ -3706,10 +3709,16 @@
JUCE Modules\juce_gui_extra
-
+
JUCE Modules\juce_javascript\javascript
-
+
+ JUCE Modules\juce_javascript\javascript
+
+
+ JUCE Modules\juce_javascript\javascript
+
+
JUCE Modules\juce_javascript\javascript
@@ -7971,7 +7980,16 @@
JUCE Modules\juce_javascript\choc\text
-
+
+ JUCE Modules\juce_javascript\detail
+
+
+ JUCE Modules\juce_javascript\javascript
+
+
+ JUCE Modules\juce_javascript\javascript
+
+
JUCE Modules\juce_javascript\javascript
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj
index 720f962f13..f206cbdb3c 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj
+++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj
@@ -2927,10 +2927,16 @@
true
-
+
true
-
+
+ true
+
+
+ true
+
+
true
@@ -4444,7 +4450,10 @@
-
+
+
+
+
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters
index 53565aa419..97053ec167 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters
+++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters
@@ -716,6 +716,9 @@
{D2777265-A166-EDB8-938A-47D1EDAADA42}
+
+ {D98D147D-FDDB-12D7-2C59-B686F458CE18}
+
{AF145034-7775-F013-E219-381BA369C108}
@@ -3706,10 +3709,16 @@
JUCE Modules\juce_gui_extra
-
+
JUCE Modules\juce_javascript\javascript
-
+
+ JUCE Modules\juce_javascript\javascript
+
+
+ JUCE Modules\juce_javascript\javascript
+
+
JUCE Modules\juce_javascript\javascript
@@ -7971,7 +7980,16 @@
JUCE Modules\juce_javascript\choc\text
-
+
+ JUCE Modules\juce_javascript\detail
+
+
+ JUCE Modules\juce_javascript\javascript
+
+
+ JUCE Modules\juce_javascript\javascript
+
+
JUCE Modules\juce_javascript\javascript
diff --git a/modules/juce_javascript/javascript/juce_Javascript.cpp b/modules/juce_javascript/detail/juce_QuickJSHelpers.h
similarity index 51%
rename from modules/juce_javascript/javascript/juce_Javascript.cpp
rename to modules/juce_javascript/detail/juce_QuickJSHelpers.h
index 2c9bbfd388..0246780e6d 100644
--- a/modules/juce_javascript/javascript/juce_Javascript.cpp
+++ b/modules/juce_javascript/detail/juce_QuickJSHelpers.h
@@ -32,7 +32,7 @@
==============================================================================
*/
-namespace juce
+namespace juce::detail
{
//==============================================================================
@@ -45,7 +45,7 @@ template
static int64_t toJuceInt64 (const T& convertible) { return (int64) (int64_t) convertible; }
//==============================================================================
-namespace qjs = detail::choc::javascript::quickjs;
+namespace qjs = choc::javascript::quickjs;
using VarOrError = std::variant;
@@ -401,7 +401,7 @@ static VarOrError quickJSToJuce (const qjs::QuickJSContext::ValuePtr& ptr)
{
return tryQuickJSToJuce (ptr);
}
- catch (const detail::choc::javascript::Error& error)
+ catch (const choc::javascript::Error& error)
{
return String (error.what());
}
@@ -412,7 +412,7 @@ static VarOrError quickJSToJuce (const qjs::QuickJSContext::ValuePtr& ptr)
// this with GCC. Suppressing this warning is fine, since these classes are only visible and used
// in a single translation unit.
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wsubobject-linkage")
-class detail::QuickJSWrapper
+class QuickJSWrapper
{
public:
qjs::JSContext* getQuickJSContext() const
@@ -589,221 +589,12 @@ struct DynamicObjectWrapper
}
//==============================================================================
- detail::QuickJSWrapper& engine;
+ QuickJSWrapper& engine;
DynamicObject::Ptr object;
std::map ordinals;
std::vector identifiers;
};
-//==============================================================================
-class JavascriptEngine::Impl
-{
-public:
- using ValuePtr = qjs::QuickJSContext::ValuePtr;
-
- //==============================================================================
- Impl()
- {
- DynamicObjectWrapper::createClass (engine.getQuickJSRuntime());
-
- engine.setInterruptHandler ([this]
- {
- return (int64) Time::getMillisecondCounterHiRes() >= timeout;
- });
- }
-
- void registerNativeObject (const Identifier& name,
- DynamicObject::Ptr dynamicObject,
- std::optional parent = std::nullopt)
- {
- auto wrapper = std::make_unique (engine, dynamicObject);
- auto* ctx = engine.getQuickJSContext();
- auto jsObject = JS_NewObjectClass (ctx, (int) DynamicObjectWrapper::getClassId());
- qjs::JS_SetOpaque (jsObject, (void*) wrapper.get());
-
- std::vector propertyFunctionList;
-
- for (const auto& [identifier, prop] : wrapper->getProperties())
- {
- auto* jsIdentifier = identifier.toString().toRawUTF8();
-
- if (prop.isMethod())
- {
- qjs::JS_SetPropertyStr (ctx,
- jsObject,
- jsIdentifier,
- JS_NewCFunctionMagic (ctx,
- DynamicObjectWrapper::callDispatcher,
- jsIdentifier,
- 0,
- qjs::JS_CFUNC_generic_magic,
- wrapper->getOrdinal (identifier)));
- }
- else if (prop.isObject())
- {
- if (auto* embeddedObject = prop.getDynamicObject())
- registerNativeObject (identifier, embeddedObject, jsObject);
- }
- else
- {
- const auto entry = makeFunctionListEntry (jsIdentifier,
- DynamicObjectWrapper::getDispatcher,
- DynamicObjectWrapper::setDispatcher,
- wrapper->getOrdinal (identifier));
- propertyFunctionList.push_back (entry);
- }
- }
-
- if (! propertyFunctionList.empty())
- {
- qjs::JS_SetPropertyFunctionList (ctx,
- jsObject,
- propertyFunctionList.data(),
- (int) propertyFunctionList.size());
- }
-
- const auto jsObjectName = name.toString().toRawUTF8();
-
- if (parent.has_value())
- {
- qjs::JS_SetPropertyStr (ctx, *parent, jsObjectName, jsObject);
- }
- else
- {
- ValuePtr globalObject { qjs::JS_GetGlobalObject (ctx), ctx };
- qjs::JS_SetPropertyStr (ctx, globalObject.get(), jsObjectName, jsObject);
- }
-
- wrapper.release();
- }
-
- var evaluate (const String& code, Result* errorMessage, RelativeTime maxExecTime)
- {
- resetTimeout (maxExecTime);
-
- if (errorMessage != nullptr)
- *errorMessage = Result::ok();
-
- const auto result = quickJSToJuce ({ JS_Eval (engine.getQuickJSContext(), code.toRawUTF8(), code.getNumBytesAsUTF8(), "", JS_EVAL_TYPE_GLOBAL), engine.getQuickJSContext() });
-
- if (auto* v = std::get_if (&result))
- return *v;
-
- if (auto* e = std::get_if (&result))
- if (errorMessage != nullptr)
- *errorMessage = Result::fail (*e);
-
- return var::undefined();
- }
-
- Result execute (const String& code, RelativeTime maxExecTime)
- {
- auto result = Result::ok();
- evaluate (code, &result, maxExecTime);
- return result;
- }
-
- var callFunction (const Identifier& function,
- const var::NativeFunctionArgs& args,
- Result* errorMessage,
- RelativeTime maxExecTime)
- {
- resetTimeout (maxExecTime);
-
- auto* ctx = engine.getQuickJSContext();
- const auto functionStr = function.toString();
-
- const auto fn = qjs::JS_NewAtomLen (ctx, functionStr.toRawUTF8(), functionStr.getNumBytesAsUTF8());
-
- JSFunctionArguments argList { ctx, args };
-
- qjs::QuickJSContext::ValuePtr global { JS_GetGlobalObject (ctx), ctx };
- qjs::QuickJSContext::ValuePtr returnVal { JS_Invoke (ctx, global.get(), fn, argList.getSize(), argList.getArguments()), ctx };
-
- JS_FreeAtom (ctx, fn);
-
- if (errorMessage != nullptr)
- *errorMessage = Result::ok();
-
- const auto result = quickJSToJuce (returnVal);
-
- if (auto* v = std::get_if (&result))
- return *v;
-
- if (auto* e = std::get_if (&result))
- if (errorMessage != nullptr)
- *errorMessage = Result::fail (*e);
-
- return var::undefined();
- }
-
- void stop() noexcept
- {
- timeout = (int64) Time::getMillisecondCounterHiRes();
- }
-
- JSObject getRootObject() const
- {
- return JSObject { &engine };
- }
-
-private:
- //==============================================================================
- void resetTimeout (RelativeTime maxExecTime)
- {
- timeout = (int64) Time::getMillisecondCounterHiRes() + maxExecTime.inMilliseconds();
- }
-
- detail::QuickJSWrapper engine;
- std::atomic timeout{};
-};
-
-//==============================================================================
-JavascriptEngine::JavascriptEngine()
- : maximumExecutionTime (15.0),
- impl (std::make_unique())
-{
-}
-
-JavascriptEngine::~JavascriptEngine() = default;
-
-void JavascriptEngine::registerNativeObject (const Identifier& name, DynamicObject* object)
-{
- impl->registerNativeObject (name, object);
-}
-
-Result JavascriptEngine::execute (const String& javascriptCode)
-{
- return impl->execute (javascriptCode, maximumExecutionTime);
-}
-
-var JavascriptEngine::evaluate (const String& javascriptCode, Result* errorMessage)
-{
- return impl->evaluate (javascriptCode, errorMessage, maximumExecutionTime);
-}
-
-var JavascriptEngine::callFunction (const Identifier& function,
- const var::NativeFunctionArgs& args,
- Result* errorMessage)
-{
- return impl->callFunction (function, args, errorMessage, maximumExecutionTime);
-}
-
-void JavascriptEngine::stop() noexcept
-{
- impl->stop();
-}
-
-JSObject JavascriptEngine::getRootObject() const
-{
- return impl->getRootObject();
-}
-
-NamedValueSet JavascriptEngine::getRootObjectProperties() const
-{
- return getRootObject().getProperties();
-}
-
//==============================================================================
static bool hasProperty (qjs::JSContext* ctx, qjs::JSValueConst object, const char* name)
{
@@ -827,433 +618,4 @@ static uint32_t toUint32 (int64 value)
return (uint32_t) value;
}
-//==============================================================================
-JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wsubobject-linkage")
-class JSObject::Impl
-{
-public:
- using ValuePtr = qjs::QuickJSContext::ValuePtr;
-
- explicit Impl (const detail::QuickJSWrapper* engineIn)
- : Impl (engineIn,
- { qjs::JS_GetGlobalObject (engineIn->getQuickJSContext()), engineIn->getQuickJSContext() })
- {
- }
-
- Impl (const Impl& other)
- : Impl (other.engine,
- { qjs::JS_DupValue (other.engine->getQuickJSContext(), other.valuePtr.get()),
- other.engine->getQuickJSContext() })
- {
- }
-
- std::unique_ptr getChild (const Identifier& prop) const
- {
- return rawToUniquePtr (new Impl (engine, { getOrCreateProperty (engine->getQuickJSContext(),
- valuePtr.get(),
- prop.toString().toRawUTF8()),
- engine->getQuickJSContext() }));
- }
-
- std::unique_ptr getChild (int64 index) const
- {
- jassert (isArray());
- return rawToUniquePtr (new Impl (engine, valuePtr[toUint32 (index)]));
- }
-
- bool hasProperty (const Identifier& name) const
- {
- return juce::hasProperty (engine->getQuickJSContext(), valuePtr.get(), name.toString().toRawUTF8());
- }
-
- void setProperty (const Identifier& name, const var& value) const
- {
- auto* ctx = engine->getQuickJSContext();
-
- qjs::JS_SetPropertyStr (ctx, valuePtr.get(), name.toString().toRawUTF8(), juceToQuickJs (value, ctx));
- }
-
- void setProperty (int64 index, const var& value) const
- {
- auto* ctx = engine->getQuickJSContext();
-
- qjs::JS_SetPropertyInt64 (ctx, valuePtr.get(), index, juceToQuickJs (value, ctx));
- }
-
- var get() const
- {
- if (auto* opaque = qjs::JS_GetOpaque (valuePtr.get(), DynamicObjectWrapper::getClassId()))
- if (DynamicObjectWrapper::getDynamicObjects().count (opaque) != 0)
- return { static_cast (opaque)->object.get() };
-
- auto* ctx = engine->getQuickJSContext();
- return discardError (quickJSToJuce ({ qjs::JS_DupValue (ctx, valuePtr.get()), ctx }));
- }
-
- VarOrError invokeMethod (const Identifier& methodName, Span args) const
- {
- if (! hasProperty (methodName))
- {
- jassertfalse;
- return {};
- }
-
- auto* ctx = engine->getQuickJSContext();
- const auto methodAtom = JS_NewAtom (ctx, methodName.toString().toRawUTF8());
- ScopeGuard scope { [&] { qjs::JS_FreeAtom (ctx, methodAtom); } };
-
- JSFunctionArguments arguments { ctx, args };
-
- ValuePtr returnVal { qjs::JS_Invoke (ctx,
- valuePtr.get(),
- methodAtom,
- arguments.getSize(),
- arguments.getArguments()),
- ctx };
-
- return quickJSToJuce (returnVal);
- }
-
- NamedValueSet getProperties() const
- {
- NamedValueSet result;
-
- auto* ctx = engine->getQuickJSContext();
- ValuePtr names { qjs::JS_GetOwnPropertyNames2 (ctx,
- valuePtr.get(),
- qjs::JS_GPN_ENUM_ONLY | qjs::JS_GPN_STRING_MASK,
- qjs::JS_ITERATOR_KIND_KEY),
- ctx };
-
-
- if (auto v = discardError (quickJSToJuce (names)); const auto* propertyNames = v.getArray())
- {
- for (const auto& name : *propertyNames)
- {
- if (name.isString())
- {
- const Identifier prop { name.toString() };
- result.set (prop, getChild (prop)->get());
- }
- }
- }
-
- return result;
- }
-
- bool isArray() const
- {
- return qjs::JS_IsArray (engine->getQuickJSContext(), valuePtr.get());
- }
-
- int64 getSize() const
- {
- if (! isArray())
- {
- jassertfalse;
- return 0;
- }
-
- auto lengthProp = valuePtr["length"];
- uint32_t length = 0;
- qjs::JS_ToUint32 (engine->getQuickJSContext(), &length, lengthProp.get());
- return (int64) length;
- }
-
-private:
- Impl (const detail::QuickJSWrapper* e, ValuePtr&& ptr)
- : engine (e), valuePtr (std::move (ptr))
- {
- }
-
- const detail::QuickJSWrapper* engine = nullptr;
- ValuePtr valuePtr;
-};
-JUCE_END_IGNORE_WARNINGS_GCC_LIKE
-
-JSObject::JSObject (const detail::QuickJSWrapper* engine)
- : impl (new Impl (engine))
-{
-}
-
-JSObject::JSObject (std::unique_ptr implIn)
- : impl (std::move (implIn))
-{
-}
-
-JSObject::JSObject (const JSObject& other)
- : impl (new Impl (*other.impl))
-{
-}
-
-JSObject::~JSObject() = default;
-
-JSObject::JSObject (JSObject&&) noexcept = default;
-
-JSObject& JSObject::operator= (const JSObject& other)
-{
- JSObject { other }.swap (*this);
- return *this;
-}
-
-JSObject& JSObject::operator= (JSObject&& other) noexcept = default;
-
-JSObject JSObject::getChild (const Identifier& name) const
-{
- return JSObject { impl->getChild (name) };
-}
-
-JSObject JSObject::operator[] (const Identifier& name) const
-{
- return getChild (name);
-}
-
-bool JSObject::isArray() const
-{
- return impl->isArray();
-}
-
-int64 JSObject::getSize() const
-{
- return impl->getSize();
-}
-
-JSObject JSObject::getChild (int64 index) const
-{
- jassert (isArray());
- return JSObject { impl->getChild (index) };
-}
-
-JSObject JSObject::operator[] (int64 index) const
-{
- return getChild (index);
-}
-
-bool JSObject::hasProperty (const Identifier& name) const
-{
- return impl->hasProperty (name);
-}
-
-var JSObject::get() const
-{
- return impl->get();
-}
-
-void JSObject::setProperty (const Identifier& name, const var& value) const
-{
- impl->setProperty (name, value);
-}
-
-void JSObject::setProperty (int64 index, const var& value) const
-{
- impl->setProperty (index, value);
-}
-
-var JSObject::invokeMethod (const Identifier& methodName,
- Span args,
- Result* result) const
-{
- const auto varOrError = impl->invokeMethod (methodName, args);
-
- if (result != nullptr)
- {
- const auto* e = std::get_if (&varOrError);
- *result = e != nullptr ? Result::fail (*e) : Result::ok();
- }
-
- return discardError (varOrError);
-}
-
-NamedValueSet JSObject::getProperties() const
-{
- return impl->getProperties();
-}
-
-void JSObject::swap (JSObject& other) noexcept
-{
- std::swap (impl, other.impl);
-}
-
-//==============================================================================
-JSCursor::JSCursor (JSObject rootIn) : root (std::move (rootIn))
-{
-}
-
-var JSCursor::get() const
-{
- if (const auto resolved = getFullResolution())
- return resolved->get();
-
- return var::undefined();
-}
-
-void JSCursor::set (const var& value) const
-{
- const auto resolved = getPartialResolution();
-
- if (! resolved.has_value())
- {
- jassertfalse; // Can't resolve an Object to change along the path stored in the cursor
- return;
- }
-
- const auto& [object, property] = *resolved;
-
- if (! property.has_value())
- {
- jassertfalse; // Can't set the value of the root Object
- return;
- }
-
- if (auto* prop = std::get_if (&(*property)))
- {
- object.setProperty (*prop, value);
- return;
- }
-
- if (auto* prop = std::get_if (&(*property)))
- {
- object.setProperty (*prop, value);
- return;
- }
-}
-
-JSCursor JSCursor::getChild (const Identifier& name) const
-{
- auto copy = *this;
- copy.path.emplace_back (name);
- return copy;
-}
-
-JSCursor JSCursor::operator[] (const Identifier& name) const
-{
- return getChild (name);
-}
-
-JSCursor JSCursor::getChild (int64 index) const
-{
- auto copy = *this;
- copy.path.emplace_back (index);
- return copy;
-}
-
-JSCursor JSCursor::operator[] (int64 index) const
-{
- return getChild (index);
-}
-
-JSObject JSCursor::getOrCreateObject() const
-{
- const auto resolved = getPartialResolution();
- jassert (resolved.has_value());
-
- const auto& [object, property] = *resolved;
-
- if (! property.has_value())
- return object;
-
- auto* integerValue = std::get_if (&(*property));
-
- jassert (integerValue == nullptr
- || (object.isArray() && (*integerValue) < object.getSize()));
-
- if (integerValue != nullptr)
- return object[*integerValue];
-
- auto* prop = std::get_if (&(*property));
- jassert(prop != nullptr);
- return object[*prop];
-}
-
-bool JSCursor::isValid() const
-{
- return getPartialResolution().has_value();
-}
-
-bool JSCursor::isArray() const
-{
- if (auto resolved = getFullResolution())
- return resolved->isArray();
-
- return false;
-}
-
-var JSCursor::invoke (Span args, Result* result) const
-{
- const auto resolved = getPartialResolution();
-
- if (! resolved.has_value())
- {
- jassertfalse;
- return {};
- }
-
- const auto& [object, property] = *resolved;
- if (! property.has_value())
- {
- jassertfalse;
- return {};
- }
-
- return object.invokeMethod (*std::get_if (&(*property)), args, result);
-}
-
-std::optional JSCursor::resolve (JSObject object, Property property)
-{
- if (auto* index = std::get_if (&property))
- {
- if (! object.isArray())
- return std::nullopt;
-
- if (! (*index < object.getSize()))
- return std::nullopt;
-
- return object[*index];
- }
-
- if (auto* key = std::get_if (&property))
- {
- if (! object.hasProperty (*key))
- return std::nullopt;
-
- return object[*key];
- }
-
- jassertfalse;
- return std::nullopt;
-}
-
-std::optional JSCursor::getPartialResolution() const
-{
- auto object = root;
-
- for (int i = 0, iEnd = (int) path.size() - 1; i < iEnd; ++i)
- {
- const auto& property = path[(size_t) i];
- auto objectOpt = resolve (object, property);
-
- if (! objectOpt.has_value())
- return std::nullopt;
-
- object = *objectOpt;
- }
-
- return std::make_optional (std::move (object),
- path.empty() ? std::nullopt
- : std::make_optional (path.back()));
-}
-
-std::optional JSCursor::getFullResolution() const
-{
- if (auto partiallyResolved = getPartialResolution())
- {
- if (! partiallyResolved->second.has_value())
- return partiallyResolved->first;
-
- return resolve (partiallyResolved->first, *(partiallyResolved->second));
- }
-
- return std::nullopt;
-}
-
-} // namespace juce
+} // namespace juce::detail
diff --git a/modules/juce_javascript/javascript/juce_JSCursor.cpp b/modules/juce_javascript/javascript/juce_JSCursor.cpp
new file mode 100644
index 0000000000..a6c7729b5d
--- /dev/null
+++ b/modules/juce_javascript/javascript/juce_JSCursor.cpp
@@ -0,0 +1,219 @@
+/*
+ ==============================================================================
+
+ This file is part of the JUCE framework.
+ Copyright (c) Raw Material Software Limited
+
+ JUCE is an open source framework subject to commercial or open source
+ licensing.
+
+ By downloading, installing, or using the JUCE framework, or combining the
+ JUCE framework with any other source code, object code, content or any other
+ copyrightable work, you agree to the terms of the JUCE End User Licence
+ Agreement, and all incorporated terms including the JUCE Privacy Policy and
+ the JUCE Website Terms of Service, as applicable, which will bind you. If you
+ do not agree to the terms of these agreements, we will not license the JUCE
+ framework to you, and you must discontinue the installation or download
+ process and cease use of the JUCE framework.
+
+ JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
+ JUCE Privacy Policy: https://juce.com/juce-privacy-policy
+ JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
+
+ Or:
+
+ You may also use this code under the terms of the AGPLv3:
+ https://www.gnu.org/licenses/agpl-3.0.en.html
+
+ THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
+ WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
+
+ ==============================================================================
+*/
+
+namespace juce
+{
+
+JSCursor::JSCursor (JSObject rootIn) : root (std::move (rootIn))
+{
+}
+
+var JSCursor::get() const
+{
+ if (const auto resolved = getFullResolution())
+ return resolved->get();
+
+ return var::undefined();
+}
+
+void JSCursor::set (const var& value) const
+{
+ const auto resolved = getPartialResolution();
+
+ if (! resolved.has_value())
+ {
+ jassertfalse; // Can't resolve an Object to change along the path stored in the cursor
+ return;
+ }
+
+ const auto& [object, property] = *resolved;
+
+ if (! property.has_value())
+ {
+ jassertfalse; // Can't set the value of the root Object
+ return;
+ }
+
+ if (auto* prop = std::get_if (&(*property)))
+ {
+ object.setProperty (*prop, value);
+ return;
+ }
+
+ if (auto* prop = std::get_if (&(*property)))
+ {
+ object.setProperty (*prop, value);
+ return;
+ }
+}
+
+JSCursor JSCursor::getChild (const Identifier& name) const
+{
+ auto copy = *this;
+ copy.path.emplace_back (name);
+ return copy;
+}
+
+JSCursor JSCursor::operator[] (const Identifier& name) const
+{
+ return getChild (name);
+}
+
+JSCursor JSCursor::getChild (int64 index) const
+{
+ auto copy = *this;
+ copy.path.emplace_back (index);
+ return copy;
+}
+
+JSCursor JSCursor::operator[] (int64 index) const
+{
+ return getChild (index);
+}
+
+JSObject JSCursor::getOrCreateObject() const
+{
+ const auto resolved = getPartialResolution();
+ jassert (resolved.has_value());
+
+ const auto& [object, property] = *resolved;
+
+ if (! property.has_value())
+ return object;
+
+ auto* integerValue = std::get_if (&(*property));
+
+ jassert (integerValue == nullptr
+ || (object.isArray() && (*integerValue) < object.getSize()));
+
+ if (integerValue != nullptr)
+ return object[*integerValue];
+
+ auto* prop = std::get_if (&(*property));
+ jassert(prop != nullptr);
+ return object[*prop];
+}
+
+bool JSCursor::isValid() const
+{
+ return getPartialResolution().has_value();
+}
+
+bool JSCursor::isArray() const
+{
+ if (auto resolved = getFullResolution())
+ return resolved->isArray();
+
+ return false;
+}
+
+var JSCursor::invoke (Span args, Result* result) const
+{
+ const auto resolved = getPartialResolution();
+
+ if (! resolved.has_value())
+ {
+ jassertfalse;
+ return {};
+ }
+
+ const auto& [object, property] = *resolved;
+ if (! property.has_value())
+ {
+ jassertfalse;
+ return {};
+ }
+
+ return object.invokeMethod (*std::get_if (&(*property)), args, result);
+}
+
+std::optional JSCursor::resolve (JSObject object, Property property)
+{
+ if (auto* index = std::get_if (&property))
+ {
+ if (! object.isArray())
+ return std::nullopt;
+
+ if (! (*index < object.getSize()))
+ return std::nullopt;
+
+ return object[*index];
+ }
+
+ if (auto* key = std::get_if (&property))
+ {
+ if (! object.hasProperty (*key))
+ return std::nullopt;
+
+ return object[*key];
+ }
+
+ jassertfalse;
+ return std::nullopt;
+}
+
+std::optional JSCursor::getPartialResolution() const
+{
+ auto object = root;
+
+ for (int i = 0, iEnd = (int) path.size() - 1; i < iEnd; ++i)
+ {
+ const auto& property = path[(size_t) i];
+ auto objectOpt = resolve (object, property);
+
+ if (! objectOpt.has_value())
+ return std::nullopt;
+
+ object = *objectOpt;
+ }
+
+ return std::make_optional (std::move (object),
+ path.empty() ? std::nullopt
+ : std::make_optional (path.back()));
+}
+
+std::optional JSCursor::getFullResolution() const
+{
+ if (auto partiallyResolved = getPartialResolution())
+ {
+ if (! partiallyResolved->second.has_value())
+ return partiallyResolved->first;
+
+ return resolve (partiallyResolved->first, *(partiallyResolved->second));
+ }
+
+ return std::nullopt;
+}
+
+} // namespace juce
diff --git a/modules/juce_javascript/javascript/juce_JSCursor.h b/modules/juce_javascript/javascript/juce_JSCursor.h
new file mode 100644
index 0000000000..4e86bfb29a
--- /dev/null
+++ b/modules/juce_javascript/javascript/juce_JSCursor.h
@@ -0,0 +1,193 @@
+/*
+ ==============================================================================
+
+ This file is part of the JUCE framework.
+ Copyright (c) Raw Material Software Limited
+
+ JUCE is an open source framework subject to commercial or open source
+ licensing.
+
+ By downloading, installing, or using the JUCE framework, or combining the
+ JUCE framework with any other source code, object code, content or any other
+ copyrightable work, you agree to the terms of the JUCE End User Licence
+ Agreement, and all incorporated terms including the JUCE Privacy Policy and
+ the JUCE Website Terms of Service, as applicable, which will bind you. If you
+ do not agree to the terms of these agreements, we will not license the JUCE
+ framework to you, and you must discontinue the installation or download
+ process and cease use of the JUCE framework.
+
+ JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
+ JUCE Privacy Policy: https://juce.com/juce-privacy-policy
+ JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
+
+ Or:
+
+ You may also use this code under the terms of the AGPLv3:
+ https://www.gnu.org/licenses/agpl-3.0.en.html
+
+ THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
+ WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
+
+ ==============================================================================
+*/
+
+namespace juce
+{
+
+/**
+ A high-level wrapper around an owning root JSObject and a hierarchical path relative to it.
+
+ It can be used to query and manipulate the location relative to the root JSObject in the
+ Javascript Object graph. A cursor only maintains ownership of the root Object. So as long as a
+ cursor points at the root it will always remain in a valid state, and isValid will return true.
+
+ Using getChild you can add elements to the cursor's relative path. You need to ensure that the
+ cursor is in a valid state when calling get or set in such cases. You can use the isValid
+ function to determine if the cursor currently points to a reachable location.
+
+ @tags{Core}
+*/
+class JUCE_API JSCursor
+{
+public:
+ /** Creates a JSCursor that points to the provided root object and also participates in its
+ ownership. This guarantees that this root object will remain valid for the lifetime of
+ this cursor.
+
+ Child JSCursors created by getChild() will contain this same root object and each will
+ further ensure that this root remains valid through reference counting.
+
+ While the validity of the root is ensured through shared ownership, the JSCursor itself is
+ not guaranteed to be valid, unless its also pointing directly at the root.
+
+ @see isValid
+ */
+ explicit JSCursor (JSObject root);
+
+ /** Returns an owning reference to the Javascript Object at the cursor's location. If there is
+ no Object at the location but the cursor is valid, a new Object will be created.
+
+ You must only call this function on a valid JSCursor.
+
+ By creating an owning reference, you can create a new JSCursor object that owns the
+ underlying object and is guaranteed to remain in a valid state e.g.
+
+ @code
+ JSCursor rootCursor { engine.getRootObject() };
+ auto nonOwningCursor = rootCursor["path"]["to"]["object"];
+
+ jassert (nonOwningCursor.isValid());
+
+ JSCursor owningCursor { nonOwningCursor.getOrCreateObject(); };
+ engine.execute (arbitraryScript);
+
+ // owningCursor is guaranteed to remain valid even after subsequent script evaluations
+ jassert (owningCursor.isValid());
+ @endcode
+
+ @see isValid
+ */
+ JSObject getOrCreateObject() const;
+
+ /** Returns the value corresponding to the Object that the cursor points to. If there is no
+ Object at the cursor's location var::undefined() is returned.
+
+ This function is safe to call for invalid cursors.
+
+ @see isValid
+ */
+ var get() const;
+
+ /** Sets the Object under the cursor's location to the specified value.
+
+ You must only call this function for valid cursors.
+
+ @see isValid
+ */
+ void set (const var& value) const;
+
+ /** Invokes this node as though it were a method. If the optional Result pointer is provided it
+ will contain Result::ok() in case of success, or an error message in case an exception was
+ thrown during evaluation.
+
+ You must only call this function for valid cursors.
+ */
+ var invoke (Span args, Result* result = nullptr) const;
+
+ /** Equivalent to invoke(). */
+ var operator() (Span args, Result* result = nullptr) const
+ {
+ return invoke (args, result);
+ }
+
+ /** Returns a new cursor that has the same root Object as the parent and has the name parameter
+ appended to the cursor's location.
+
+ If the new path points to a location unreachable from the root, the resulting JSCursor
+ object will be invalid. This however can change due to subsequent script executions.
+ */
+ JSCursor getChild (const Identifier& name) const;
+
+ /** Returns a new cursor that has the same root Object as the parent and has the name parameter
+ appended to the cursor's location.
+
+ If the new path points to a location unreachable from the root, the resulting JSCursor
+ object will be invalid. This however can change due to subsequent script executions.
+ Shorthand for getChild.
+ */
+ JSCursor operator[] (const Identifier& name) const;
+
+ /** Returns a new cursor that has the same root Object as the parent and has the index parameter
+ appended to the cursor's location. This overload will create a path that indexes into an
+ Array.
+
+ If the new path points to a location unreachable from the root, the resulting JSCursor
+ object will be invalid. This however can change due to subsequent script executions.
+ */
+ JSCursor getChild (int64 index) const;
+
+ /** Returns a new cursor that has the same root Object as the parent and has the index parameter
+ appended to the cursor's location. This overload will create a path that indexes into an
+ Array.
+
+ If the new path points to a location unreachable from the root, the resulting JSCursor
+ object will be invalid. This however can change due to subsequent script executions.
+ Shorthand for getChild.
+ */
+ JSCursor operator[] (int64 index) const;
+
+ /** Returns true if the location of the cursor is reachable from the cursor's JSObject root.
+ This means it is safe to call set on this JSCursor and the location will then point to an
+ Object corresponding to the supplied value.
+
+ It isn't guaranteed that there is already an Object at this location, in which case calling
+ get will return var::undefined().
+ */
+ bool isValid() const;
+
+ /** Returns true if there is an Array under the cursor's location.
+
+ It is safe to call this function on an invalid cursor.
+ */
+ bool isArray() const;
+
+private:
+ using Property = std::variant;
+ using PartialResolution = std::pair>;
+
+ static std::optional resolve (JSObject reference, Property property);
+
+ // Resolves the path to the second to last element. By taking ownership (creating an object for)
+ // of the second to last element, the result of a successful partial resolution can be used to
+ // construct the last element if it doesn't yet exist.
+ std::optional getPartialResolution() const;
+
+ // Fully resolves the path and takes ownership of the object that was specified by it.
+ std::optional getFullResolution() const;
+
+ JSObject root;
+ std::vector path;
+};
+
+} // namespace juce
diff --git a/modules/juce_javascript/javascript/juce_JSObject.cpp b/modules/juce_javascript/javascript/juce_JSObject.cpp
new file mode 100644
index 0000000000..f7fdd60591
--- /dev/null
+++ b/modules/juce_javascript/javascript/juce_JSObject.cpp
@@ -0,0 +1,284 @@
+/*
+ ==============================================================================
+
+ This file is part of the JUCE framework.
+ Copyright (c) Raw Material Software Limited
+
+ JUCE is an open source framework subject to commercial or open source
+ licensing.
+
+ By downloading, installing, or using the JUCE framework, or combining the
+ JUCE framework with any other source code, object code, content or any other
+ copyrightable work, you agree to the terms of the JUCE End User Licence
+ Agreement, and all incorporated terms including the JUCE Privacy Policy and
+ the JUCE Website Terms of Service, as applicable, which will bind you. If you
+ do not agree to the terms of these agreements, we will not license the JUCE
+ framework to you, and you must discontinue the installation or download
+ process and cease use of the JUCE framework.
+
+ JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
+ JUCE Privacy Policy: https://juce.com/juce-privacy-policy
+ JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
+
+ Or:
+
+ You may also use this code under the terms of the AGPLv3:
+ https://www.gnu.org/licenses/agpl-3.0.en.html
+
+ THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
+ WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
+
+ ==============================================================================
+*/
+
+namespace juce
+{
+
+JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wsubobject-linkage")
+class JSObject::Impl
+{
+public:
+ using ValuePtr = detail::qjs::QuickJSContext::ValuePtr;
+
+ explicit Impl (const detail::QuickJSWrapper* engineIn)
+ : Impl (engineIn,
+ { detail::qjs::JS_GetGlobalObject (engineIn->getQuickJSContext()), engineIn->getQuickJSContext() })
+ {
+ }
+
+ Impl (const Impl& other)
+ : Impl (other.engine,
+ { detail::qjs::JS_DupValue (other.engine->getQuickJSContext(), other.valuePtr.get()),
+ other.engine->getQuickJSContext() })
+ {
+ }
+
+ std::unique_ptr getChild (const Identifier& prop) const
+ {
+ return rawToUniquePtr (new Impl (engine, { detail::getOrCreateProperty (engine->getQuickJSContext(),
+ valuePtr.get(),
+ prop.toString().toRawUTF8()),
+ engine->getQuickJSContext() }));
+ }
+
+ std::unique_ptr getChild (int64 index) const
+ {
+ jassert (isArray());
+ return rawToUniquePtr (new Impl (engine, valuePtr[detail::toUint32 (index)]));
+ }
+
+ bool hasProperty (const Identifier& name) const
+ {
+ return detail::hasProperty (engine->getQuickJSContext(), valuePtr.get(), name.toString().toRawUTF8());
+ }
+
+ void setProperty (const Identifier& name, const var& value) const
+ {
+ auto* ctx = engine->getQuickJSContext();
+
+ detail::qjs::JS_SetPropertyStr (ctx, valuePtr.get(), name.toString().toRawUTF8(), detail::juceToQuickJs (value, ctx));
+ }
+
+ void setProperty (int64 index, const var& value) const
+ {
+ auto* ctx = engine->getQuickJSContext();
+
+ detail::qjs::JS_SetPropertyInt64 (ctx, valuePtr.get(), index, detail::juceToQuickJs (value, ctx));
+ }
+
+ var get() const
+ {
+ if (auto* opaque = detail::qjs::JS_GetOpaque (valuePtr.get(), detail::DynamicObjectWrapper::getClassId()))
+ if (detail::DynamicObjectWrapper::getDynamicObjects().count (opaque) != 0)
+ return { static_cast (opaque)->object.get() };
+
+ auto* ctx = engine->getQuickJSContext();
+ return detail::discardError (detail::quickJSToJuce ({ detail::qjs::JS_DupValue (ctx, valuePtr.get()), ctx }));
+ }
+
+ detail::VarOrError invokeMethod (const Identifier& methodName, Span args) const
+ {
+ if (! hasProperty (methodName))
+ {
+ jassertfalse;
+ return {};
+ }
+
+ auto* ctx = engine->getQuickJSContext();
+ const auto methodAtom = JS_NewAtom (ctx, methodName.toString().toRawUTF8());
+ ScopeGuard scope { [&] { detail::qjs::JS_FreeAtom (ctx, methodAtom); } };
+
+ detail::JSFunctionArguments arguments { ctx, args };
+
+ ValuePtr returnVal { detail::qjs::JS_Invoke (ctx,
+ valuePtr.get(),
+ methodAtom,
+ arguments.getSize(),
+ arguments.getArguments()),
+ ctx };
+
+ return detail::quickJSToJuce (returnVal);
+ }
+
+ NamedValueSet getProperties() const
+ {
+ NamedValueSet result;
+
+ auto* ctx = engine->getQuickJSContext();
+ ValuePtr names { detail::qjs::JS_GetOwnPropertyNames2 (ctx,
+ valuePtr.get(),
+ detail::qjs::JS_GPN_ENUM_ONLY | detail::qjs::JS_GPN_STRING_MASK,
+ detail::qjs::JS_ITERATOR_KIND_KEY),
+ ctx };
+
+
+ if (auto v = detail::discardError (detail::quickJSToJuce (names)); const auto* propertyNames = v.getArray())
+ {
+ for (const auto& name : *propertyNames)
+ {
+ if (name.isString())
+ {
+ const Identifier prop { name.toString() };
+ result.set (prop, getChild (prop)->get());
+ }
+ }
+ }
+
+ return result;
+ }
+
+ bool isArray() const
+ {
+ return detail::qjs::JS_IsArray (engine->getQuickJSContext(), valuePtr.get());
+ }
+
+ int64 getSize() const
+ {
+ if (! isArray())
+ {
+ jassertfalse;
+ return 0;
+ }
+
+ auto lengthProp = valuePtr["length"];
+ uint32_t length = 0;
+ detail::qjs::JS_ToUint32 (engine->getQuickJSContext(), &length, lengthProp.get());
+ return (int64) length;
+ }
+
+private:
+ Impl (const detail::QuickJSWrapper* e, ValuePtr&& ptr)
+ : engine (e), valuePtr (std::move (ptr))
+ {
+ }
+
+ const detail::QuickJSWrapper* engine = nullptr;
+ ValuePtr valuePtr;
+};
+JUCE_END_IGNORE_WARNINGS_GCC_LIKE
+
+JSObject::JSObject (const detail::QuickJSWrapper* engine)
+ : impl (new Impl (engine))
+{
+}
+
+JSObject::JSObject (std::unique_ptr implIn)
+ : impl (std::move (implIn))
+{
+}
+
+JSObject::JSObject (const JSObject& other)
+ : impl (new Impl (*other.impl))
+{
+}
+
+JSObject::~JSObject() = default;
+
+JSObject::JSObject (JSObject&&) noexcept = default;
+
+JSObject& JSObject::operator= (const JSObject& other)
+{
+ JSObject { other }.swap (*this);
+ return *this;
+}
+
+JSObject& JSObject::operator= (JSObject&& other) noexcept = default;
+
+JSObject JSObject::getChild (const Identifier& name) const
+{
+ return JSObject { impl->getChild (name) };
+}
+
+JSObject JSObject::operator[] (const Identifier& name) const
+{
+ return getChild (name);
+}
+
+bool JSObject::isArray() const
+{
+ return impl->isArray();
+}
+
+int64 JSObject::getSize() const
+{
+ return impl->getSize();
+}
+
+JSObject JSObject::getChild (int64 index) const
+{
+ jassert (isArray());
+ return JSObject { impl->getChild (index) };
+}
+
+JSObject JSObject::operator[] (int64 index) const
+{
+ return getChild (index);
+}
+
+bool JSObject::hasProperty (const Identifier& name) const
+{
+ return impl->hasProperty (name);
+}
+
+var JSObject::get() const
+{
+ return impl->get();
+}
+
+void JSObject::setProperty (const Identifier& name, const var& value) const
+{
+ impl->setProperty (name, value);
+}
+
+void JSObject::setProperty (int64 index, const var& value) const
+{
+ impl->setProperty (index, value);
+}
+
+var JSObject::invokeMethod (const Identifier& methodName,
+ Span args,
+ Result* result) const
+{
+ const auto varOrError = impl->invokeMethod (methodName, args);
+
+ if (result != nullptr)
+ {
+ const auto* e = std::get_if (&varOrError);
+ *result = e != nullptr ? Result::fail (*e) : Result::ok();
+ }
+
+ return detail::discardError (varOrError);
+}
+
+NamedValueSet JSObject::getProperties() const
+{
+ return impl->getProperties();
+}
+
+void JSObject::swap (JSObject& other) noexcept
+{
+ std::swap (impl, other.impl);
+}
+
+} // namespace juce
diff --git a/modules/juce_javascript/javascript/juce_JSObject.h b/modules/juce_javascript/javascript/juce_JSObject.h
new file mode 100644
index 0000000000..ee606da926
--- /dev/null
+++ b/modules/juce_javascript/javascript/juce_JSObject.h
@@ -0,0 +1,168 @@
+/*
+ ==============================================================================
+
+ This file is part of the JUCE framework.
+ Copyright (c) Raw Material Software Limited
+
+ JUCE is an open source framework subject to commercial or open source
+ licensing.
+
+ By downloading, installing, or using the JUCE framework, or combining the
+ JUCE framework with any other source code, object code, content or any other
+ copyrightable work, you agree to the terms of the JUCE End User Licence
+ Agreement, and all incorporated terms including the JUCE Privacy Policy and
+ the JUCE Website Terms of Service, as applicable, which will bind you. If you
+ do not agree to the terms of these agreements, we will not license the JUCE
+ framework to you, and you must discontinue the installation or download
+ process and cease use of the JUCE framework.
+
+ JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
+ JUCE Privacy Policy: https://juce.com/juce-privacy-policy
+ JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
+
+ Or:
+
+ You may also use this code under the terms of the AGPLv3:
+ https://www.gnu.org/licenses/agpl-3.0.en.html
+
+ THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
+ WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
+
+ ==============================================================================
+*/
+
+namespace juce
+{
+
+/**
+ A JSObject represents an owning reference to the underlying JS object, meaning it will remain
+ valid even if a subsequent script execution deletes other handles to it.
+
+ Objects of this class can be used to traverse the current object graph inside the specified
+ Javascript engine.
+
+ This is a low-level providing only operations that map directly to the underlying Javascript
+ Object implementation. The JSCursor class generally provides a more convenient interface with
+ functions that may fail based on the Javascript engine's current state.
+
+ @see JSCursor
+ @tags{Core}
+*/
+class JUCE_API JSObject
+{
+public:
+ /** Constructor, used internally by the JavascriptEngine implementation.
+
+ To create a new JSObject pointing at the root object of the engine's context use
+ JavascriptEngine::getRootObject().
+ */
+ explicit JSObject (const detail::QuickJSWrapper* engine);
+
+ /** Destructor. */
+ ~JSObject();
+
+ /** Copy constructor. */
+ JSObject (const JSObject&);
+
+ /** Move constructor. */
+ JSObject (JSObject&&) noexcept;
+
+ /** Copy assignment operator. */
+ JSObject& operator= (const JSObject&);
+
+ /** Move assignment operator. */
+ JSObject& operator= (JSObject&&) noexcept;
+
+ /** Returns a new cursor pointing to a JS object that is a property of the parent cursor's
+ underlying object and has the provided name.
+
+ You can use hasProperty() to check if such a property exists prior to the creation of this
+ cursor. If no such property exists, this constructor will create a new JS Object and attach
+ it to the parent under the specified name. This can be used to manipulate the object graph.
+ */
+ JSObject getChild (const Identifier& name) const;
+
+ /** Returns a cursor object pointing to the property with the given name. If such property
+ doesn't exist it will be created as an empty JS Object. Shorthand for getChild.
+ */
+ JSObject operator[] (const Identifier& name) const;
+
+ /** Returns a new cursor object pointing to the specified element in an Array.
+
+ You must ensure that the cursor points to an Array before calling this function.
+
+ @see isArray
+ */
+ JSObject getChild (int64 index) const;
+
+ /** Returns a new cursor object pointing to the specified element in an Array. This function is
+ a shorthand for getChild (int64).
+
+ You must ensure that the cursor points to an Array before calling this function.
+
+ @see isArray
+ */
+ JSObject operator[] (int64 index) const;
+
+ /** Returns true if the JS Object under the cursor is an Array.
+
+ You can use getChild() or operator[]() to get a cursor to individual elements in the
+ array or get() to obtain a JUCE variant wrapping all array elements.
+ */
+ bool isArray() const;
+
+ /** Returns the size of the underlying JS Array.
+
+ You must ensure that the cursor points to an Array before calling this function.
+
+ @see isArray
+ */
+ int64 getSize() const;
+
+ /** Returns true if the object under the cursor has a property with the given name. */
+ bool hasProperty (const Identifier& name) const;
+
+ /** Returns a variant with a value of the property under the given name. If no such property
+ exists an undefined variant is returned.
+
+ If this property points to an object created by JavascriptEngine::registerNativeObject(),
+ then the returned variant will contain a pointer to the original object and can be acquired
+ by variant::getDynamicObject().
+ */
+ var get() const;
+
+ /** Adds a named property to the underlying Object with the provided value, or assigns this
+ value to an existing property with this name.
+ */
+ void setProperty (const Identifier& name, const var& value) const;
+
+ /** Adds a property with an integral identifier and the provided value to the underlying Object,
+ or assigns the value to an existing property.
+
+ If the underlying Object is also an Array, then the provided value will be assigned to the
+ specified element of this Array, and ensure that it will have a size of at least index - 1.
+ */
+ void setProperty (int64 index, const var& value) const;
+
+ /** Invokes this node as though it were a method.
+
+ If the optional Result pointer is provided it will contain Result::ok() in case of success,
+ or an error message in case an exception was thrown during evaluation.
+ */
+ var invokeMethod (const Identifier& methodName, Span args, Result* result = nullptr) const;
+
+ /** Returns all properties of the current object that are own properties, i.e. not inherited. */
+ NamedValueSet getProperties() const;
+
+private:
+ class Impl;
+
+ explicit JSObject (std::unique_ptr implIn);
+
+ void swap (JSObject& other) noexcept;
+
+ std::unique_ptr impl;
+};
+
+} // namespace juce
diff --git a/modules/juce_javascript/javascript/juce_Javascript.h b/modules/juce_javascript/javascript/juce_Javascript.h
deleted file mode 100644
index 3e32307161..0000000000
--- a/modules/juce_javascript/javascript/juce_Javascript.h
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- ==============================================================================
-
- This file is part of the JUCE framework.
- Copyright (c) Raw Material Software Limited
-
- JUCE is an open source framework subject to commercial or open source
- licensing.
-
- By downloading, installing, or using the JUCE framework, or combining the
- JUCE framework with any other source code, object code, content or any other
- copyrightable work, you agree to the terms of the JUCE End User Licence
- Agreement, and all incorporated terms including the JUCE Privacy Policy and
- the JUCE Website Terms of Service, as applicable, which will bind you. If you
- do not agree to the terms of these agreements, we will not license the JUCE
- framework to you, and you must discontinue the installation or download
- process and cease use of the JUCE framework.
-
- JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
- JUCE Privacy Policy: https://juce.com/juce-privacy-policy
- JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
-
- Or:
-
- You may also use this code under the terms of the AGPLv3:
- https://www.gnu.org/licenses/agpl-3.0.en.html
-
- THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
- WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
- MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
-
- ==============================================================================
-*/
-
-namespace juce
-{
-
-/**
- A JSObject represents an owning reference to the underlying JS object, meaning it will remain
- valid even if a subsequent script execution deletes other handles to it.
-
- Objects of this class can be used to traverse the current object graph inside the specified
- Javascript engine.
-
- This is a low-level providing only operations that map directly to the underlying Javascript
- Object implementation. The JSCursor class generally provides a more convenient interface with
- functions that may fail based on the Javascript engine's current state.
-
- @see JSCursor
- @tags{Core}
-*/
-class JUCE_API JSObject
-{
-public:
- /** Constructor, used internally by the JavascriptEngine implementation.
-
- To create a new JSObject pointing at the root object of the engine's context use
- JavascriptEngine::getRootObject().
- */
- explicit JSObject (const detail::QuickJSWrapper* engine);
-
- /** Destructor. */
- ~JSObject();
-
- /** Copy constructor. */
- JSObject (const JSObject&);
-
- /** Move constructor. */
- JSObject (JSObject&&) noexcept;
-
- /** Copy assignment operator. */
- JSObject& operator= (const JSObject&);
-
- /** Move assignment operator. */
- JSObject& operator= (JSObject&&) noexcept;
-
- /** Returns a new cursor pointing to a JS object that is a property of the parent cursor's
- underlying object and has the provided name.
-
- You can use hasProperty() to check if such a property exists prior to the creation of this
- cursor. If no such property exists, this constructor will create a new JS Object and attach
- it to the parent under the specified name. This can be used to manipulate the object graph.
- */
- JSObject getChild (const Identifier& name) const;
-
- /** Returns a cursor object pointing to the property with the given name. If such property
- doesn't exist it will be created as an empty JS Object. Shorthand for getChild.
- */
- JSObject operator[] (const Identifier& name) const;
-
- /** Returns a new cursor object pointing to the specified element in an Array.
-
- You must ensure that the cursor points to an Array before calling this function.
-
- @see isArray
- */
- JSObject getChild (int64 index) const;
-
- /** Returns a new cursor object pointing to the specified element in an Array. This function is
- a shorthand for getChild (int64).
-
- You must ensure that the cursor points to an Array before calling this function.
-
- @see isArray
- */
- JSObject operator[] (int64 index) const;
-
- /** Returns true if the JS Object under the cursor is an Array.
-
- You can use getChild() or operator[]() to get a cursor to individual elements in the
- array or get() to obtain a JUCE variant wrapping all array elements.
- */
- bool isArray() const;
-
- /** Returns the size of the underlying JS Array.
-
- You must ensure that the cursor points to an Array before calling this function.
-
- @see isArray
- */
- int64 getSize() const;
-
- /** Returns true if the object under the cursor has a property with the given name. */
- bool hasProperty (const Identifier& name) const;
-
- /** Returns a variant with a value of the property under the given name. If no such property
- exists an undefined variant is returned.
-
- If this property points to an object created by JavascriptEngine::registerNativeObject(),
- then the returned variant will contain a pointer to the original object and can be acquired
- by variant::getDynamicObject().
- */
- var get() const;
-
- /** Adds a named property to the underlying Object with the provided value, or assigns this
- value to an existing property with this name.
- */
- void setProperty (const Identifier& name, const var& value) const;
-
- /** Adds a property with an integral identifier and the provided value to the underlying Object,
- or assigns the value to an existing property.
-
- If the underlying Object is also an Array, then the provided value will be assigned to the
- specified element of this Array, and ensure that it will have a size of at least index - 1.
- */
- void setProperty (int64 index, const var& value) const;
-
- /** Invokes this node as though it were a method.
-
- If the optional Result pointer is provided it will contain Result::ok() in case of success,
- or an error message in case an exception was thrown during evaluation.
- */
- var invokeMethod (const Identifier& methodName, Span args, Result* result = nullptr) const;
-
- /** Returns all properties of the current object that are own properties, i.e. not inherited. */
- NamedValueSet getProperties() const;
-
-private:
- class Impl;
-
- explicit JSObject (std::unique_ptr implIn);
-
- void swap (JSObject& other) noexcept;
-
- std::unique_ptr impl;
-};
-
-/**
- A high-level wrapper around an owning root JSObject and a hierarchical path relative to it.
-
- It can be used to query and manipulate the location relative to the root JSObject in the
- Javascript Object graph. A cursor only maintains ownership of the root Object. So as long as a
- cursor points at the root it will always remain in a valid state, and isValid will return true.
-
- Using getChild you can add elements to the cursor's relative path. You need to ensure that the
- cursor is in a valid state when calling get or set in such cases. You can use the isValid
- function to determine if the cursor currently points to a reachable location.
-
- @tags{Core}
-*/
-class JUCE_API JSCursor
-{
-public:
- /** Creates a JSCursor that points to the provided root object and also participates in its
- ownership. This guarantees that this root object will remain valid for the lifetime of
- this cursor.
-
- Child JSCursors created by getChild() will contain this same root object and each will
- further ensure that this root remains valid through reference counting.
-
- While the validity of the root is ensured through shared ownership, the JSCursor itself is
- not guaranteed to be valid, unless its also pointing directly at the root.
-
- @see isValid
- */
- explicit JSCursor (JSObject root);
-
- /** Returns an owning reference to the Javascript Object at the cursor's location. If there is
- no Object at the location but the cursor is valid, a new Object will be created.
-
- You must only call this function on a valid JSCursor.
-
- By creating an owning reference, you can create a new JSCursor object that owns the
- underlying object and is guaranteed to remain in a valid state e.g.
-
- @code
- JSCursor rootCursor { engine.getRootObject() };
- auto nonOwningCursor = rootCursor["path"]["to"]["object"];
-
- jassert (nonOwningCursor.isValid());
-
- JSCursor owningCursor { nonOwningCursor.getOrCreateObject(); };
- engine.execute (arbitraryScript);
-
- // owningCursor is guaranteed to remain valid even after subsequent script evaluations
- jassert (owningCursor.isValid());
- @endcode
-
- @see isValid
- */
- JSObject getOrCreateObject() const;
-
- /** Returns the value corresponding to the Object that the cursor points to. If there is no
- Object at the cursor's location var::undefined() is returned.
-
- This function is safe to call for invalid cursors.
-
- @see isValid
- */
- var get() const;
-
- /** Sets the Object under the cursor's location to the specified value.
-
- You must only call this function for valid cursors.
-
- @see isValid
- */
- void set (const var& value) const;
-
- /** Invokes this node as though it were a method. If the optional Result pointer is provided it
- will contain Result::ok() in case of success, or an error message in case an exception was
- thrown during evaluation.
-
- You must only call this function for valid cursors.
- */
- var invoke (Span args, Result* result = nullptr) const;
-
- /** Equivalent to invoke(). */
- var operator() (Span args, Result* result = nullptr) const
- {
- return invoke (args, result);
- }
-
- /** Returns a new cursor that has the same root Object as the parent and has the name parameter
- appended to the cursor's location.
-
- If the new path points to a location unreachable from the root, the resulting JSCursor
- object will be invalid. This however can change due to subsequent script executions.
- */
- JSCursor getChild (const Identifier& name) const;
-
- /** Returns a new cursor that has the same root Object as the parent and has the name parameter
- appended to the cursor's location.
-
- If the new path points to a location unreachable from the root, the resulting JSCursor
- object will be invalid. This however can change due to subsequent script executions.
- Shorthand for getChild.
- */
- JSCursor operator[] (const Identifier& name) const;
-
- /** Returns a new cursor that has the same root Object as the parent and has the index parameter
- appended to the cursor's location. This overload will create a path that indexes into an
- Array.
-
- If the new path points to a location unreachable from the root, the resulting JSCursor
- object will be invalid. This however can change due to subsequent script executions.
- */
- JSCursor getChild (int64 index) const;
-
- /** Returns a new cursor that has the same root Object as the parent and has the index parameter
- appended to the cursor's location. This overload will create a path that indexes into an
- Array.
-
- If the new path points to a location unreachable from the root, the resulting JSCursor
- object will be invalid. This however can change due to subsequent script executions.
- Shorthand for getChild.
- */
- JSCursor operator[] (int64 index) const;
-
- /** Returns true if the location of the cursor is reachable from the cursor's JSObject root.
- This means it is safe to call set on this JSCursor and the location will then point to an
- Object corresponding to the supplied value.
-
- It isn't guaranteed that there is already an Object at this location, in which case calling
- get will return var::undefined().
- */
- bool isValid() const;
-
- /** Returns true if there is an Array under the cursor's location.
-
- It is safe to call this function on an invalid cursor.
- */
- bool isArray() const;
-
-private:
- using Property = std::variant;
- using PartialResolution = std::pair>;
-
- static std::optional resolve (JSObject reference, Property property);
-
- // Resolves the path to the second to last element. By taking ownership (creating an object for)
- // of the second to last element, the result of a successful partial resolution can be used to
- // construct the last element if it doesn't yet exist.
- std::optional getPartialResolution() const;
-
- // Fully resolves the path and takes ownership of the object that was specified by it.
- std::optional getFullResolution() const;
-
- JSObject root;
- std::vector path;
-};
-
-//==============================================================================
-/**
- A simple javascript interpreter!
-
- It's not fully standards-compliant, and won't be as fast as the fancy JIT-compiled
- engines that you get in browsers, but this is an extremely compact, low-overhead javascript
- interpreter, which is integrated with the juce var and DynamicObject classes. If you need
- a few simple bits of scripting in your app, and want to be able to easily let the JS
- work with native objects defined as DynamicObject subclasses, then this might do the job.
-
- To use, simply create an instance of this class and call execute() to run your code.
- Variables that the script sets can be retrieved with evaluate(), and if you need to provide
- native objects for the script to use, you can add them with registerNativeObject().
-
- One caveat: Because the values and objects that the engine works with are DynamicObject
- and var objects, they use reference-counting rather than garbage-collection, so if your
- script creates complex connections between objects, you run the risk of creating cyclic
- dependencies and hence leaking.
-
- @tags{Core}
-*/
-class JUCE_API JavascriptEngine final
-{
-public:
- /** Creates an instance of the engine.
- */
- JavascriptEngine();
-
- /** Destructor. */
- ~JavascriptEngine();
-
- /** Attempts to parse and run a block of javascript code.
- If there's a parse or execution error, the error description is returned in
- the result.
- You can specify a maximum time for which the program is allowed to run, and
- it'll return with an error message if this time is exceeded.
- */
- Result execute (const String& javascriptCode);
-
- /** Attempts to parse and run a javascript expression, and returns the result.
- If there's a syntax error, or the expression can't be evaluated, the return value
- will be var::undefined(). The errorMessage parameter gives you a way to find out
- any parsing errors.
- If the expression is successfully evaluated but yields no result the return value
- will be a void var.
- You can specify a maximum time for which the program is allowed to run, and
- it'll return with an error message if this time is exceeded.
- */
- var evaluate (const String& javascriptCode,
- Result* errorMessage = nullptr);
-
- /** Calls a function in the root namespace, and returns the result.
- The function arguments are passed in the same format as used by native
- methods in the var class.
- */
- var callFunction (const Identifier& function,
- const var::NativeFunctionArgs& args,
- Result* errorMessage = nullptr);
-
- /** Adds a native object to the root namespace.
- The object passed-in is reference-counted, and will be retained by the
- engine until the engine is deleted. The name must be a simple JS identifier,
- without any dots.
- */
- void registerNativeObject (const Identifier& objectName, DynamicObject* object);
-
- /** This value indicates how long a call to one of the evaluate methods is permitted
- to run before timing-out and failing.
- The default value is a number of seconds, but you can change this to whatever value
- suits your application.
- */
- RelativeTime maximumExecutionTime;
-
- /** When called from another thread, causes the interpreter to time-out as soon as possible */
- void stop() noexcept;
-
- /** Returns the object from which all Javascript objects are reachable in the engine's context.
- */
- JSObject getRootObject() const;
-
- /** Provides access to the set of properties of the root namespace object. */
- NamedValueSet getRootObjectProperties() const;
-
-private:
- class Impl;
- std::unique_ptr impl;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JavascriptEngine)
- JUCE_DECLARE_NON_MOVEABLE (JavascriptEngine)
-};
-
-} // namespace juce
diff --git a/modules/juce_javascript/javascript/juce_JavascriptEngine.cpp b/modules/juce_javascript/javascript/juce_JavascriptEngine.cpp
new file mode 100644
index 0000000000..1fff0d88d2
--- /dev/null
+++ b/modules/juce_javascript/javascript/juce_JavascriptEngine.cpp
@@ -0,0 +1,247 @@
+/*
+ ==============================================================================
+
+ This file is part of the JUCE framework.
+ Copyright (c) Raw Material Software Limited
+
+ JUCE is an open source framework subject to commercial or open source
+ licensing.
+
+ By downloading, installing, or using the JUCE framework, or combining the
+ JUCE framework with any other source code, object code, content or any other
+ copyrightable work, you agree to the terms of the JUCE End User Licence
+ Agreement, and all incorporated terms including the JUCE Privacy Policy and
+ the JUCE Website Terms of Service, as applicable, which will bind you. If you
+ do not agree to the terms of these agreements, we will not license the JUCE
+ framework to you, and you must discontinue the installation or download
+ process and cease use of the JUCE framework.
+
+ JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
+ JUCE Privacy Policy: https://juce.com/juce-privacy-policy
+ JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
+
+ Or:
+
+ You may also use this code under the terms of the AGPLv3:
+ https://www.gnu.org/licenses/agpl-3.0.en.html
+
+ THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
+ WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
+
+ ==============================================================================
+*/
+
+namespace juce
+{
+
+//==============================================================================
+class JavascriptEngine::Impl
+{
+public:
+ using ValuePtr = detail::qjs::QuickJSContext::ValuePtr;
+
+ //==============================================================================
+ Impl()
+ {
+ detail::DynamicObjectWrapper::createClass (engine.getQuickJSRuntime());
+
+ engine.setInterruptHandler ([this]
+ {
+ return (int64) Time::getMillisecondCounterHiRes() >= timeout;
+ });
+ }
+
+ void registerNativeObject (const Identifier& name,
+ DynamicObject::Ptr dynamicObject,
+ std::optional parent = std::nullopt)
+ {
+ auto wrapper = std::make_unique (engine, dynamicObject);
+ auto* ctx = engine.getQuickJSContext();
+ auto jsObject = JS_NewObjectClass (ctx, (int) detail::DynamicObjectWrapper::getClassId());
+ detail::qjs::JS_SetOpaque (jsObject, (void*) wrapper.get());
+
+ std::vector propertyFunctionList;
+
+ for (const auto& [identifier, prop] : wrapper->getProperties())
+ {
+ auto* jsIdentifier = identifier.toString().toRawUTF8();
+
+ if (prop.isMethod())
+ {
+ detail::qjs::JS_SetPropertyStr (ctx,
+ jsObject,
+ jsIdentifier,
+ JS_NewCFunctionMagic (ctx,
+ detail::DynamicObjectWrapper::callDispatcher,
+ jsIdentifier,
+ 0,
+ detail::qjs::JS_CFUNC_generic_magic,
+ wrapper->getOrdinal (identifier)));
+ }
+ else if (prop.isObject())
+ {
+ if (auto* embeddedObject = prop.getDynamicObject())
+ registerNativeObject (identifier, embeddedObject, jsObject);
+ }
+ else
+ {
+ const auto entry = detail::makeFunctionListEntry (jsIdentifier,
+ detail::DynamicObjectWrapper::getDispatcher,
+ detail::DynamicObjectWrapper::setDispatcher,
+ wrapper->getOrdinal (identifier));
+ propertyFunctionList.push_back (entry);
+ }
+ }
+
+ if (! propertyFunctionList.empty())
+ {
+ detail::qjs::JS_SetPropertyFunctionList (ctx,
+ jsObject,
+ propertyFunctionList.data(),
+ (int) propertyFunctionList.size());
+ }
+
+ const auto jsObjectName = name.toString().toRawUTF8();
+
+ if (parent.has_value())
+ {
+ detail::qjs::JS_SetPropertyStr (ctx, *parent, jsObjectName, jsObject);
+ }
+ else
+ {
+ ValuePtr globalObject { detail::qjs::JS_GetGlobalObject (ctx), ctx };
+ detail::qjs::JS_SetPropertyStr (ctx, globalObject.get(), jsObjectName, jsObject);
+ }
+
+ wrapper.release();
+ }
+
+ var evaluate (const String& code, Result* errorMessage, RelativeTime maxExecTime)
+ {
+ resetTimeout (maxExecTime);
+
+ if (errorMessage != nullptr)
+ *errorMessage = Result::ok();
+
+ const auto result = detail::quickJSToJuce ({ JS_Eval (engine.getQuickJSContext(), code.toRawUTF8(), code.getNumBytesAsUTF8(), "", JS_EVAL_TYPE_GLOBAL), engine.getQuickJSContext() });
+
+ if (auto* v = std::get_if (&result))
+ return *v;
+
+ if (auto* e = std::get_if (&result))
+ if (errorMessage != nullptr)
+ *errorMessage = Result::fail (*e);
+
+ return var::undefined();
+ }
+
+ Result execute (const String& code, RelativeTime maxExecTime)
+ {
+ auto result = Result::ok();
+ evaluate (code, &result, maxExecTime);
+ return result;
+ }
+
+ var callFunction (const Identifier& function,
+ const var::NativeFunctionArgs& args,
+ Result* errorMessage,
+ RelativeTime maxExecTime)
+ {
+ resetTimeout (maxExecTime);
+
+ auto* ctx = engine.getQuickJSContext();
+ const auto functionStr = function.toString();
+
+ const auto fn = detail::qjs::JS_NewAtomLen (ctx, functionStr.toRawUTF8(), functionStr.getNumBytesAsUTF8());
+
+ detail::JSFunctionArguments argList { ctx, args };
+
+ detail::qjs::QuickJSContext::ValuePtr global { JS_GetGlobalObject (ctx), ctx };
+ detail::qjs::QuickJSContext::ValuePtr returnVal { JS_Invoke (ctx, global.get(), fn, argList.getSize(), argList.getArguments()), ctx };
+
+ JS_FreeAtom (ctx, fn);
+
+ if (errorMessage != nullptr)
+ *errorMessage = Result::ok();
+
+ const auto result = detail::quickJSToJuce (returnVal);
+
+ if (auto* v = std::get_if (&result))
+ return *v;
+
+ if (auto* e = std::get_if (&result))
+ if (errorMessage != nullptr)
+ *errorMessage = Result::fail (*e);
+
+ return var::undefined();
+ }
+
+ void stop() noexcept
+ {
+ timeout = (int64) Time::getMillisecondCounterHiRes();
+ }
+
+ JSObject getRootObject() const
+ {
+ return JSObject { &engine };
+ }
+
+private:
+ //==============================================================================
+ void resetTimeout (RelativeTime maxExecTime)
+ {
+ timeout = (int64) Time::getMillisecondCounterHiRes() + maxExecTime.inMilliseconds();
+ }
+
+ detail::QuickJSWrapper engine;
+ std::atomic timeout{};
+};
+
+//==============================================================================
+JavascriptEngine::JavascriptEngine()
+ : maximumExecutionTime (15.0),
+ impl (std::make_unique())
+{
+}
+
+JavascriptEngine::~JavascriptEngine() = default;
+
+void JavascriptEngine::registerNativeObject (const Identifier& name, DynamicObject* object)
+{
+ impl->registerNativeObject (name, object);
+}
+
+Result JavascriptEngine::execute (const String& javascriptCode)
+{
+ return impl->execute (javascriptCode, maximumExecutionTime);
+}
+
+var JavascriptEngine::evaluate (const String& javascriptCode, Result* errorMessage)
+{
+ return impl->evaluate (javascriptCode, errorMessage, maximumExecutionTime);
+}
+
+var JavascriptEngine::callFunction (const Identifier& function,
+ const var::NativeFunctionArgs& args,
+ Result* errorMessage)
+{
+ return impl->callFunction (function, args, errorMessage, maximumExecutionTime);
+}
+
+void JavascriptEngine::stop() noexcept
+{
+ impl->stop();
+}
+
+JSObject JavascriptEngine::getRootObject() const
+{
+ return impl->getRootObject();
+}
+
+NamedValueSet JavascriptEngine::getRootObjectProperties() const
+{
+ return getRootObject().getProperties();
+}
+
+} // namespace juce
diff --git a/modules/juce_javascript/javascript/juce_JavascriptEngine.h b/modules/juce_javascript/javascript/juce_JavascriptEngine.h
new file mode 100644
index 0000000000..426a05400f
--- /dev/null
+++ b/modules/juce_javascript/javascript/juce_JavascriptEngine.h
@@ -0,0 +1,128 @@
+/*
+ ==============================================================================
+
+ This file is part of the JUCE framework.
+ Copyright (c) Raw Material Software Limited
+
+ JUCE is an open source framework subject to commercial or open source
+ licensing.
+
+ By downloading, installing, or using the JUCE framework, or combining the
+ JUCE framework with any other source code, object code, content or any other
+ copyrightable work, you agree to the terms of the JUCE End User Licence
+ Agreement, and all incorporated terms including the JUCE Privacy Policy and
+ the JUCE Website Terms of Service, as applicable, which will bind you. If you
+ do not agree to the terms of these agreements, we will not license the JUCE
+ framework to you, and you must discontinue the installation or download
+ process and cease use of the JUCE framework.
+
+ JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
+ JUCE Privacy Policy: https://juce.com/juce-privacy-policy
+ JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
+
+ Or:
+
+ You may also use this code under the terms of the AGPLv3:
+ https://www.gnu.org/licenses/agpl-3.0.en.html
+
+ THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
+ WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
+
+ ==============================================================================
+*/
+
+namespace juce
+{
+
+/**
+ A simple javascript interpreter!
+
+ It's not fully standards-compliant, and won't be as fast as the fancy JIT-compiled
+ engines that you get in browsers, but this is an extremely compact, low-overhead javascript
+ interpreter, which is integrated with the juce var and DynamicObject classes. If you need
+ a few simple bits of scripting in your app, and want to be able to easily let the JS
+ work with native objects defined as DynamicObject subclasses, then this might do the job.
+
+ To use, simply create an instance of this class and call execute() to run your code.
+ Variables that the script sets can be retrieved with evaluate(), and if you need to provide
+ native objects for the script to use, you can add them with registerNativeObject().
+
+ One caveat: Because the values and objects that the engine works with are DynamicObject
+ and var objects, they use reference-counting rather than garbage-collection, so if your
+ script creates complex connections between objects, you run the risk of creating cyclic
+ dependencies and hence leaking.
+
+ @tags{Core}
+*/
+class JUCE_API JavascriptEngine final
+{
+public:
+ /** Creates an instance of the engine.
+ */
+ JavascriptEngine();
+
+ /** Destructor. */
+ ~JavascriptEngine();
+
+ /** Attempts to parse and run a block of javascript code.
+ If there's a parse or execution error, the error description is returned in
+ the result.
+ You can specify a maximum time for which the program is allowed to run, and
+ it'll return with an error message if this time is exceeded.
+ */
+ Result execute (const String& javascriptCode);
+
+ /** Attempts to parse and run a javascript expression, and returns the result.
+ If there's a syntax error, or the expression can't be evaluated, the return value
+ will be var::undefined(). The errorMessage parameter gives you a way to find out
+ any parsing errors.
+ If the expression is successfully evaluated but yields no result the return value
+ will be a void var.
+ You can specify a maximum time for which the program is allowed to run, and
+ it'll return with an error message if this time is exceeded.
+ */
+ var evaluate (const String& javascriptCode,
+ Result* errorMessage = nullptr);
+
+ /** Calls a function in the root namespace, and returns the result.
+ The function arguments are passed in the same format as used by native
+ methods in the var class.
+ */
+ var callFunction (const Identifier& function,
+ const var::NativeFunctionArgs& args,
+ Result* errorMessage = nullptr);
+
+ /** Adds a native object to the root namespace.
+ The object passed-in is reference-counted, and will be retained by the
+ engine until the engine is deleted. The name must be a simple JS identifier,
+ without any dots.
+ */
+ void registerNativeObject (const Identifier& objectName, DynamicObject* object);
+
+ /** This value indicates how long a call to one of the evaluate methods is permitted
+ to run before timing-out and failing.
+ The default value is a number of seconds, but you can change this to whatever value
+ suits your application.
+ */
+ RelativeTime maximumExecutionTime;
+
+ /** When called from another thread, causes the interpreter to time-out as soon as possible */
+ void stop() noexcept;
+
+ /** Returns the object from which all Javascript objects are reachable in the engine's context.
+ */
+ JSObject getRootObject() const;
+
+ /** Provides access to the set of properties of the root namespace object. */
+ NamedValueSet getRootObjectProperties() const;
+
+private:
+ class Impl;
+ std::unique_ptr impl;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JavascriptEngine)
+ JUCE_DECLARE_NON_MOVEABLE (JavascriptEngine)
+};
+
+} // namespace juce
diff --git a/modules/juce_javascript/juce_javascript.cpp b/modules/juce_javascript/juce_javascript.cpp
index 31efdbd1ca..a76ee357cb 100644
--- a/modules/juce_javascript/juce_javascript.cpp
+++ b/modules/juce_javascript/juce_javascript.cpp
@@ -52,7 +52,11 @@
#include
#undef choc
-#include "javascript/juce_Javascript.cpp"
+#include "detail/juce_QuickJSHelpers.h"
+
+#include "javascript/juce_JSObject.cpp"
+#include "javascript/juce_JSCursor.cpp"
+#include "javascript/juce_JavascriptEngine.cpp"
#if JUCE_UNIT_TESTS
#include "javascript/juce_Javascript_test.cpp"
diff --git a/modules/juce_javascript/juce_javascript.h b/modules/juce_javascript/juce_javascript.h
index 067bdbfe93..7baca087cc 100644
--- a/modules/juce_javascript/juce_javascript.h
+++ b/modules/juce_javascript/juce_javascript.h
@@ -62,4 +62,7 @@
#define JUCE_JAVASCRIPT_H_INCLUDED
#include
-#include "javascript/juce_Javascript.h"
+
+#include "javascript/juce_JSObject.h"
+#include "javascript/juce_JSCursor.h"
+#include "javascript/juce_JavascriptEngine.h"