LCOV - code coverage report
Current view: top level - muse/framework/extensions/internal - scriptengine.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.6 % 170 1
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2024 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : #include "scriptengine.h"
      23              : 
      24              : #include <QJSValueIterator>
      25              : 
      26              : #include "global/api/apiutils.h"
      27              : 
      28              : #include "jsmoduleloader.h"
      29              : 
      30              : #include "../api/extapi.h"
      31              : #include "../api/v1/extapiv1.h"
      32              : 
      33              : #include "log.h"
      34              : 
      35              : using namespace muse;
      36              : using namespace muse::extensions;
      37              : using namespace muse::api;
      38              : 
      39            0 : ScriptEngine::ScriptEngine(const modularity::ContextPtr& iocCtx, const muse::api::ApiContext& apiContext)
      40            0 :     : m_iocContext(iocCtx)
      41              : {
      42            0 :     const int apiversion = apiContext.apiversion;
      43            0 :     m_engine = new QJSEngine();
      44              : 
      45            0 :     m_engine->installExtensions(QJSEngine::ConsoleExtension);
      46            0 :     m_engine->setProperty("apiversion", apiversion);
      47            0 :     m_apiengine = new JsApiEngine(m_engine, m_iocContext, apiContext);
      48              : 
      49            0 :     QJSValue globalObj = m_engine->globalObject();
      50            0 :     if (apiversion == 1) {
      51              :         //! NOTE API v1 provides not only one global `api` object,
      52              :         //! but also a number of others, for example `curScore`,
      53              :         //! this is the legacy of the Qml plugin.
      54            0 :         apiv1::ExtApiV1* api = new apiv1::ExtApiV1(m_apiengine, m_engine);
      55            0 :         api->setup(globalObj);
      56            0 :         m_api = api;
      57              :     } else {
      58            0 :         m_api = new api::ExtApi(m_apiengine, m_engine);
      59              :     }
      60              : 
      61            0 :     globalObj.setProperty("api", m_engine->newQObject(m_api));
      62              : 
      63            0 :     const std::vector<muse::api::IApiRegister::GlobalEnum>& globalEnums = apiRegister()->globalEnums();
      64            0 :     for (const muse::api::IApiRegister::GlobalEnum& e : globalEnums) {
      65            0 :         QString name = QString::fromStdString(e.name);
      66            0 :         QJSValue enumObj = muse::api::enumToJsValue(m_apiengine, e.meta, e.type);
      67            0 :         globalObj.setProperty(name, enumObj);
      68            0 :     }
      69              : 
      70            0 :     m_moduleLoader = new JsModuleLoader(m_iocContext, m_engine);
      71            0 :     m_moduleLoader->pushEngine(this);
      72            0 :     QJSValue loaderObj = m_engine->newQObject(m_moduleLoader);
      73            0 :     QJSValue requireFn = loaderObj.property("require");
      74            0 :     globalObj.setProperty("require", requireFn);
      75            0 :     globalObj.setProperty("exports", m_engine->newObject());
      76            0 :     globalObj.setProperty("module", loaderObj);
      77            0 : }
      78              : 
      79            0 : ScriptEngine::ScriptEngine(ScriptEngine* engine)
      80            0 :     : m_iocContext(engine->m_iocContext), m_engine(engine->m_engine), m_apiengine(engine->m_apiengine), m_api(engine->m_api),
      81            0 :     m_moduleLoader(engine->m_moduleLoader),
      82            0 :     m_isRequireMode(true)
      83              : {
      84            0 :     m_moduleLoader->pushEngine(this);
      85            0 : }
      86              : 
      87            0 : ScriptEngine::~ScriptEngine()
      88              : {
      89            0 :     m_moduleLoader->popEngine();
      90              : 
      91            0 :     if (!m_isRequireMode) {
      92            0 :         delete m_moduleLoader;
      93            0 :         delete m_api;
      94            0 :         delete m_apiengine;
      95            0 :         delete m_engine;
      96              :     }
      97            0 : }
      98              : 
      99            0 : void ScriptEngine::setScriptPath(const io::path_t& arg)
     100              : {
     101            0 :     m_scriptPath = arg;
     102            0 : }
     103              : 
     104            0 : io::path_t ScriptEngine::scriptPath() const
     105              : {
     106            0 :     return m_scriptPath;
     107              : }
     108              : 
     109            0 : QJSValue ScriptEngine::requireModule(const QString& module)
     110              : {
     111            0 :     LOGD() << module;
     112            0 :     auto obj = apiRegister()->createApi(module.toStdString(), m_apiengine);
     113            0 :     if (!obj.first) {
     114            0 :         LOGE() << "failed create api for module: " << module;
     115            0 :         return QJSValue();
     116              :     }
     117            0 :     bool isNeedDelete = obj.second;
     118            0 :     QJSEngine::setObjectOwnership(obj.first, isNeedDelete ? QJSEngine::JavaScriptOwnership : QJSEngine::CppOwnership);
     119            0 :     return m_engine->newQObject(obj.first);
     120              : }
     121              : 
     122            0 : QJSValue ScriptEngine::requireFile(const QString& filePath)
     123              : {
     124            1 :     TRACEFUNC;
     125              : 
     126            0 :     RetVal<ByteArray> data = readScriptContent(filePath);
     127            0 :     if (!data.ret) {
     128            0 :         LOGE() << "failed read file, err: " << data.ret.toString() << ", file: " << filePath;
     129            0 :         return QJSValue();
     130              :     }
     131              : 
     132            0 :     QByteArray content = QByteArray("(function() { \n") + data.val.toQByteArrayNoCopy() + QByteArray("}());");
     133              : 
     134            0 :     ScriptEngine requireEngine(this);
     135            0 :     requireEngine.setScriptPath(filePath);
     136            0 :     RetVal<QJSValue> val = requireEngine.evaluateContent(content, filePath);
     137            0 :     if (!val.ret) {
     138            0 :         LOGE() << "failed evaluate content ret: " << val.ret.toString();
     139            0 :         return QJSValue();
     140              :     }
     141              : 
     142            0 :     QJSValue exports = requireEngine.globalProperty("exports");
     143            0 :     return exports;
     144            0 : }
     145              : 
     146            0 : void ScriptEngine::setGlobalProperty(const QString& name, const QJSValue& val)
     147              : {
     148            0 :     m_engine->globalObject().setProperty(name, val);
     149            0 : }
     150              : 
     151            0 : QJSValue ScriptEngine::globalProperty(const QString& name) const
     152              : {
     153            0 :     return m_engine->globalObject().property(name);
     154              : }
     155              : 
     156            0 : QJSValue ScriptEngine::exports() const
     157              : {
     158            0 :     return globalProperty("exports");
     159              : }
     160              : 
     161            0 : void ScriptEngine::setExports(const QJSValue& obj)
     162              : {
     163            0 :     setGlobalProperty("exports", obj);
     164            0 : }
     165              : 
     166            0 : Ret ScriptEngine::evaluate()
     167              : {
     168            0 :     io::path_t path = scriptPath();
     169              : 
     170            0 :     IF_ASSERT_FAILED(!path.empty()) {
     171            0 :         return make_ret(Ret::Code::InternalError);
     172              :     }
     173              : 
     174            0 :     RetVal<ByteArray> content = readScriptContent(path);
     175            0 :     if (!content.ret) {
     176            0 :         return content.ret;
     177              :     }
     178              : 
     179            0 :     const QByteArray contentData = content.val.toQByteArray();
     180              : 
     181            0 :     IF_ASSERT_FAILED(!contentData.isEmpty()) {
     182            0 :         return make_ret(Ret::Code::InternalError);
     183              :     }
     184              : 
     185            0 :     if (m_lastEvalScript != contentData) {
     186            0 :         RetVal<QJSValue> eval = evaluateContent(contentData, path);
     187            0 :         if (!eval.ret) {
     188            0 :             return eval.ret;
     189              :         }
     190            0 :         m_lastEvalScript = contentData;
     191            0 :     }
     192              : 
     193            0 :     return Ret(Ret::Code::Ok);
     194            0 : }
     195              : 
     196            0 : Ret ScriptEngine::call(const QString& funcName, const QJSValueList& args, QJSValue* retVal)
     197              : {
     198            0 :     Ret ret = doCall(funcName, args, retVal);
     199            0 :     return ret;
     200              : }
     201              : 
     202            0 : Ret ScriptEngine::doCall(const QString& funcName, const QJSValueList& args, QJSValue* retVal)
     203              : {
     204            0 :     TRACEFUNC;
     205              : 
     206            0 :     IF_ASSERT_FAILED(!funcName.isEmpty()) {
     207            0 :         return make_ret(Ret::Code::InternalError);
     208              :     }
     209              : 
     210            0 :     Ret ret = evaluate();
     211            0 :     if (!ret) {
     212            0 :         return ret;
     213              :     }
     214              : 
     215            0 :     if (m_lastCallFunc.funcName != funcName) {
     216            0 :         m_lastCallFunc.func = m_engine->globalObject().property(funcName);
     217              :     }
     218              : 
     219            0 :     ret = m_lastCallFunc.func.isCallable();
     220            0 :     if (!ret) {
     221            0 :         m_lastCallFunc.funcName.clear();
     222            0 :         ret = make_ret(Ret::Code::UnknownError, QString("not found function: `%1`").arg(funcName));
     223            0 :         LOGE() << "Not found function: " << funcName;
     224              :         //dump("globalObject", m_engine->globalObject());
     225              :     }
     226              : 
     227            0 :     QJSValue value;
     228            0 :     if (ret) {
     229            0 :         value = m_lastCallFunc.func.call(args);
     230            0 :         ret = jsValueToRet(value);
     231              :     }
     232              : 
     233            0 :     if (retVal) {
     234            0 :         *retVal = value;
     235              :     }
     236              : 
     237            0 :     return ret;
     238            0 : }
     239              : 
     240            0 : Ret ScriptEngine::jsValueToRet(const QJSValue& val)
     241              : {
     242            0 :     if (val.isError()) {
     243            0 :         QString fileName = val.property("fileName").toString();
     244            0 :         int line = val.property("lineNumber").toInt();
     245            0 :         QString err = val.toString();
     246            0 :         if (err == "Error: abort") {
     247            0 :             return make_ret(Ret::Code::Cancel, QString("script is aborted"));
     248              :         }
     249            0 :         Ret ret = make_ret(Ret::Code::UnknownError, QString("File: %1, Exception at line: %2, %3").arg(fileName).arg(line).arg(err));
     250            0 :         ret.setData("file", fileName);
     251            0 :         ret.setData("line", line);
     252            0 :         ret.setData("err", err);
     253            0 :         return ret;
     254            0 :     } else if (val.isBool()) {
     255            0 :         return Ret(val.toBool() ? Ret::Code::Ok : Ret::Code::UnknownError);
     256            0 :     } else if (val.isObject() && val.hasProperty("code")) {
     257            0 :         Ret ret;
     258            0 :         ret.setCode(val.property("code").toInt());
     259            0 :         ret.setText(val.property("text").toString().toStdString()); // maybe not present
     260            0 :         return ret;
     261            0 :     }
     262              : 
     263            0 :     return Ret(Ret::Code::Ok);
     264              : }
     265              : 
     266            0 : void ScriptEngine::dump(const QString& name, const QJSValue& val)
     267              : {
     268            0 :     QStringList props;
     269            0 :     QJSValueIterator it(val);
     270            0 :     while (it.next()) {
     271            0 :         props << it.name();
     272              :     }
     273              : 
     274            0 :     QString str = name + "\n";
     275            0 :     for (const QString& prop : props) {
     276            0 :         str += "  " + prop + ": " + val.property(prop).toString() + "\n";
     277              :     }
     278              : 
     279            0 :     LOGD() << str;
     280            0 : }
     281              : 
     282            0 : void ScriptEngine::throwError(const QString& message)
     283              : {
     284            0 :     m_engine->throwError(message);
     285            0 : }
     286              : 
     287            0 : RetVal<ByteArray> ScriptEngine::readScriptContent(const io::path_t& scriptPath) const
     288              : {
     289            0 :     TRACEFUNC;
     290            0 :     return fileSystem()->readFile(scriptPath);
     291            0 : }
     292              : 
     293            0 : RetVal<QJSValue> ScriptEngine::evaluateContent(const QByteArray& fileContent, const io::path_t& filePath)
     294              : {
     295            0 :     TRACEFUNC;
     296            0 :     RetVal<QJSValue> rv;
     297            0 :     rv.val = m_engine->evaluate(QString(fileContent), filePath.toQString());
     298            0 :     rv.ret = jsValueToRet(rv.val);
     299            0 :     return rv;
     300            0 : }
        

Generated by: LCOV version 2.0-1