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"