diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index 325277e1e9d0..a174167e61b5 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -121,7 +121,7 @@ void Controller::receive(const QByteArray data, mixxx::Duration timestamp) { continue; } function.append(".incomingData"); - QScriptValue incomingData = m_pEngine->resolveFunction(function); + QScriptValue incomingData = m_pEngine->wrapFunctionCode(function, 2); if (!m_pEngine->execute(incomingData, data, timestamp)) { qWarning() << "Controller: Invalid script function" << function; } diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index de7aa379dae0..40c70281b859 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -98,30 +98,38 @@ void ControllerEngine::callFunctionOnObjects(QList scriptFunctionPrefix } } -/* -------- ------------------------------------------------------ -Purpose: Resolves a function name to a QScriptValue including - OBJECT.Function calls -Input: - -Output: - --------- ------------------------------------------------------ */ -QScriptValue ControllerEngine::resolveFunction(const QString& function) const { - QHash::const_iterator i = - m_scriptValueCache.find(function); - if (i != m_scriptValueCache.end()) { - return i.value(); - } +/* ------------------------------------------------------------------ +Purpose: Turn a snippet of JS into a QScriptValue function. + Wrapping it in an anonymous function allows any JS that + evaluates to a function to be used in MIDI mapping XML files + and ensures the function is executed with the correct + 'this' object. +Input: QString snippet of JS that evaluates to a function, + int number of arguments that the function takes +Output: QScriptValue of JS snippet wrapped in an anonymous function +------------------------------------------------------------------- */ +QScriptValue ControllerEngine::wrapFunctionCode(const QString& codeSnippet, + int numberOfArgs) { + QScriptValue wrappedFunction; - QScriptValue object = m_pEngine->globalObject(); - QStringList parts = function.split("."); + QHash::const_iterator i = + m_scriptWrappedFunctionCache.find(codeSnippet); - for (int i = 0; i < parts.size(); i++) { - object = object.property(parts.at(i)); - if (!object.isValid()) { - break; + if (i != m_scriptWrappedFunctionCache.end()) { + wrappedFunction = i.value(); + } else { + QStringList wrapperArgList; + for (int i = 1; i <= numberOfArgs; i++) { + wrapperArgList << QString("arg%1").arg(i); } - } - m_scriptValueCache[function] = object; - return object; + QString wrapperArgs = wrapperArgList.join(","); + QString wrappedCode = "(function (" + wrapperArgs + ") { (" + + codeSnippet + ")(" + wrapperArgs + "); })"; + wrappedFunction = m_pEngine->evaluate(wrappedCode); + checkException(); + m_scriptWrappedFunctionCache[codeSnippet] = wrappedFunction; + } + return wrappedFunction; } /* -------- ------------------------------------------------------ @@ -158,8 +166,8 @@ void ControllerEngine::gracefulShutdown() { } } - // Clear the Script Value cache - m_scriptValueCache.clear(); + // Clear the cache of function wrappers + m_scriptWrappedFunctionCache.clear(); // Free all the control object threads QList keys = m_controlCache.keys(); @@ -297,19 +305,11 @@ bool ControllerEngine::evaluate(const QString& filepath) { return ret; } -/* -------- ------------------------------------------------------ -Purpose: Evaluate & run script code -Input: 'this' object if applicable, Code string -Output: false if an exception --------- ------------------------------------------------------ */ -bool ControllerEngine::internalExecute(QScriptValue thisObject, - const QString& scriptCode) { - // A special version of safeExecute since we're evaluating strings, not actual functions - // (execute() would print an error that it's not a function every time a timer fires.) - if (m_pEngine == nullptr) +bool ControllerEngine::syntaxIsValid(const QString& scriptCode) { + if (m_pEngine == nullptr) { return false; + } - // Check syntax QScriptSyntaxCheckResult result = m_pEngine->checkSyntax(scriptCode); QString error = ""; switch (result.state()) { @@ -332,6 +332,25 @@ bool ControllerEngine::internalExecute(QScriptValue thisObject, scriptErrorDialog(error); return false; } + return true; +} + +/* -------- ------------------------------------------------------ +Purpose: Evaluate & run script code +Input: 'this' object if applicable, Code string +Output: false if an exception +-------- ------------------------------------------------------ */ +bool ControllerEngine::internalExecute(QScriptValue thisObject, + const QString& scriptCode) { + // A special version of safeExecute since we're evaluating strings, not actual functions + // (execute() would print an error that it's not a function every time a timer fires.) + if (m_pEngine == nullptr) { + return false; + } + + if (!syntaxIsValid(scriptCode)) { + return false; + } QScriptValue scriptFunction = m_pEngine->evaluate(scriptCode); @@ -360,6 +379,12 @@ bool ControllerEngine::internalExecute(QScriptValue thisObject, QScriptValue fun return false; } + if (functionObject.isError()) { + qDebug() << "ControllerEngine::internalExecute:" + << functionObject.toString(); + return false; + } + // If it's not a function, we're done. if (!functionObject.isFunction()) { qDebug() << "ControllerEngine::internalExecute:" @@ -441,6 +466,8 @@ bool ControllerEngine::checkException() { scriptErrorDialog(ControllerDebug::enabled() ? QString("%1\nBacktrace:\n%2") .arg(errorText, backtrace.join("\n")) : errorText); + + m_pEngine->clearExceptions(); return true; } return false; diff --git a/src/controllers/controllerengine.h b/src/controllers/controllerengine.h index 8b5a17c95d41..549ae34ba951 100644 --- a/src/controllers/controllerengine.h +++ b/src/controllers/controllerengine.h @@ -77,8 +77,8 @@ class ControllerEngine : public QObject { m_bPopups = bPopups; } - // Resolve a function name to a QScriptValue. - QScriptValue resolveFunction(const QString& function) const; + // Wrap a snippet of JS code in an anonymous function + QScriptValue wrapFunctionCode(const QString& codeSnippet, int numberOfArgs); // Look up registered script function prefixes const QList& getScriptFunctionPrefixes() { return m_scriptFunctionPrefixes; }; @@ -148,6 +148,7 @@ class ControllerEngine : public QObject { void errorDialogButton(const QString& key, QMessageBox::StandardButton button); private: + bool syntaxIsValid(const QString& scriptCode); bool evaluate(const QString& scriptName, QList scriptPaths); bool internalExecute(QScriptValue thisObject, const QString& scriptCode); bool internalExecute(QScriptValue thisObject, QScriptValue functionObject, @@ -193,7 +194,7 @@ class ControllerEngine : public QObject { QVarLengthArray m_ramp, m_brakeActive; QVarLengthArray m_scratchFilters; QHash m_scratchTimers; - mutable QHash m_scriptValueCache; + QHash m_scriptWrappedFunctionCache; // Filesystem watcher for script auto-reload QFileSystemWatcher m_scriptWatcher; QList m_lastScriptPaths; diff --git a/src/controllers/midi/midicontroller.cpp b/src/controllers/midi/midicontroller.cpp index e4d33a1beb3f..5701f3d62c2b 100644 --- a/src/controllers/midi/midicontroller.cpp +++ b/src/controllers/midi/midicontroller.cpp @@ -288,7 +288,7 @@ void MidiController::processInputMapping(const MidiInputMapping& mapping, return; } - QScriptValue function = pEngine->resolveFunction(mapping.control.item); + QScriptValue function = pEngine->wrapFunctionCode(mapping.control.item, 5); if (!pEngine->execute(function, channel, control, value, status, mapping.control.group, timestamp)) { qDebug() << "MidiController: Invalid script function" @@ -547,7 +547,7 @@ void MidiController::processInputMapping(const MidiInputMapping& mapping, if (pEngine == NULL) { return; } - QScriptValue function = pEngine->resolveFunction(mapping.control.item); + QScriptValue function = pEngine->wrapFunctionCode(mapping.control.item, 2); if (!pEngine->execute(function, data, timestamp)) { qDebug() << "MidiController: Invalid script function" << mapping.control.item; diff --git a/src/test/controllerengine_test.cpp b/src/test/controllerengine_test.cpp index ab50cca3d5ad..5e52744d5164 100644 --- a/src/test/controllerengine_test.cpp +++ b/src/test/controllerengine_test.cpp @@ -30,7 +30,7 @@ class ControllerEngineTest : public MixxxTest { } bool execute(const QString& functionName) { - QScriptValue function = cEngine->resolveFunction(functionName); + QScriptValue function = cEngine->wrapFunctionCode(functionName, 0); return cEngine->internalExecute(QScriptValue(), function, QScriptValueList()); }