Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 5 files). Show all
Unified · Split
| @@ -73,15 +73,11 @@ · 72 unmodified lines | |||
| 73 | 73 | ||
| 74 | #include "MIDIEvent.h" | 74 | #include "MIDIEvent.h" |
| 75 | 75 | ||
| 76 | const QString ScriptEngine::_SETTINGS_ENABLE_EXTENDED_MODULE_COMPAT { | ||
| 77 | "com.highfidelity.experimental.enableExtendedModuleCompatbility" | ||
| 78 | }; | ||
| 79 | |||
| 80 | const QString ScriptEngine::_SETTINGS_ENABLE_EXTENDED_EXCEPTIONS { | 76 | const QString ScriptEngine::_SETTINGS_ENABLE_EXTENDED_EXCEPTIONS { |
| 81 | "com.highfidelity.experimental.enableExtendedJSExceptions" | 77 | "com.highfidelity.experimental.enableExtendedJSExceptions" |
| 82 | }; | 78 | }; |
| 83 | 79 | ||
| 84 | static const int MAX_MODULE_ID_LENTGH { 4096 }; | 80 | static const int MAX_MODULE_ID_LENGTH { 4096 }; |
| 85 | static const int MAX_DEBUG_VALUE_LENGTH { 80 }; | 81 | static const int MAX_DEBUG_VALUE_LENGTH { 80 }; |
| 86 | 82 | ||
| 87 | static const QScriptEngine::QObjectWrapOptions DEFAULT_QOBJECT_WRAP_OPTIONS = | 83 | static const QScriptEngine::QObjectWrapOptions DEFAULT_QOBJECT_WRAP_OPTIONS = |
| @@ -96,7 +92,7 @@ int functionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature · 8 unmodified lines | |||
| 96 | 92 | ||
| 97 | Q_LOGGING_CATEGORY(scriptengineScript, "hifi.scriptengine.script") | 93 | Q_LOGGING_CATEGORY(scriptengineScript, "hifi.scriptengine.script") |
| 98 | 94 | ||
| 99 | static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){ | 95 | static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) { |
| 100 | QString message = ""; | 96 | QString message = ""; |
| 101 | for (int i = 0; i < context->argumentCount(); i++) { | 97 | for (int i = 0; i < context->argumentCount(); i++) { |
| 102 | if (i > 0) { | 98 | if (i > 0) { |
| @@ -345,11 +341,7 @@ void ScriptEngine::runInThread() { · 242 unmodified lines | |||
| 345 | // The thread interface cannot live on itself, and we want to move this into the thread, so | 341 | // The thread interface cannot live on itself, and we want to move this into the thread, so |
| 346 | // the thread cannot have this as a parent. | 342 | // the thread cannot have this as a parent. |
| 347 | QThread* workerThread = new QThread(); | 343 | QThread* workerThread = new QThread(); |
| 348 | #ifdef Q_OS_LINUX | 344 | workerThread->setObjectName(QString("js:") + getFilename().replace("about:","")); |
| 349 | workerThread->setObjectName(QString("js:") + getFilename()); | ||
| 350 | #else | ||
| 351 | workerThread->setObjectName(QString("Script Thread:") + getFilename()); | ||
| 352 | #endif | ||
| 353 | moveToThread(workerThread); | 345 | moveToThread(workerThread); |
| 354 | 346 | ||
| 355 | // NOTE: If you connect any essential signals for proper shutdown or cleanup of | 347 | // NOTE: If you connect any essential signals for proper shutdown or cleanup of |
| @@ -550,7 +542,7 @@ static QScriptValue createScriptableResourcePrototype(QScriptEngine* engine) { · 194 unmodified lines | |||
| 550 | 542 | ||
| 551 | void ScriptEngine::resetModuleCache(bool deleteScriptCache) { | 543 | void ScriptEngine::resetModuleCache(bool deleteScriptCache) { |
| 552 | if (QThread::currentThread() != thread()) { | 544 | if (QThread::currentThread() != thread()) { |
| 553 | executeOnScriptThread([=](){ resetModuleCache(deleteScriptCache); }); | 545 | executeOnScriptThread([=]() { resetModuleCache(deleteScriptCache); }); |
| 554 | return; | 546 | return; |
| 555 | } | 547 | } |
| 556 | auto jsRequire = globalObject().property("Script").property("require"); | 548 | auto jsRequire = globalObject().property("Script").property("require"); |
| @@ -1379,14 +1371,14 @@ QString ScriptEngine::_requireResolve(const QString& moduleId, const QString& re · 822 unmodified lines | |||
| 1379 | auto throwResolveError = [&](const QScriptValue& error) -> QString { | 1371 | auto throwResolveError = [&](const QScriptValue& error) -> QString { |
| 1380 | raiseException(error); | 1372 | raiseException(error); |
| 1381 | maybeEmitUncaughtException("require.resolve"); | 1373 | maybeEmitUncaughtException("require.resolve"); |
| 1382 | return nullptr; | 1374 | return QString(); |
| 1383 | }; | 1375 | }; |
| 1384 | 1376 | ||
| 1385 | // de-fuzz the input a little by restricting to rational sizes | 1377 | // de-fuzz the input a little by restricting to rational sizes |
| 1386 | auto idLength = url.toString().length(); | 1378 | auto idLength = url.toString().length(); |
| 1387 | if (idLength < 1 || idLength > MAX_MODULE_ID_LENTGH) { | 1379 | if (idLength < 1 || idLength > MAX_MODULE_ID_LENGTH) { |
| 1388 | auto details = QString("rejecting invalid module id size (%1 chars [1,%2])") | 1380 | auto details = QString("rejecting invalid module id size (%1 chars [1,%2])") |
| 1389 | .arg(idLength).arg(MAX_MODULE_ID_LENTGH); | 1381 | .arg(idLength).arg(MAX_MODULE_ID_LENGTH); |
| 1390 | return throwResolveError(makeError(message.arg(details), "RangeError")); | 1382 | return throwResolveError(makeError(message.arg(details), "RangeError")); |
| 1391 | } | 1383 | } |
| 1392 | 1384 | ||
| @@ -1402,11 +1394,21 @@ QString ScriptEngine::_requireResolve(const QString& moduleId, const QString& re · 9 unmodified lines | |||
| 1402 | url = resolvePath(moduleId); | 1394 | url = resolvePath(moduleId); |
| 1403 | } else { | 1395 | } else { |
| 1404 | // check if the moduleId refers to a "system" module | 1396 | // check if the moduleId refers to a "system" module |
| 1405 | QString defaultsPath = defaultScriptsLoc.path(); | 1397 | QString systemPath = defaultScriptsLoc.path(); |
| 1406 | QString systemModulePath = QString("%1/modules/%2.js").arg(defaultsPath).arg(moduleId); | 1398 | QString systemModulePath = QString("%1/modules/%2.js").arg(systemPath).arg(moduleId); |
| 1407 | url = defaultScriptsLoc; | 1399 | url = defaultScriptsLoc; |
| 1408 | url.setPath(systemModulePath); | 1400 | url.setPath(systemModulePath); |
| 1409 | if (!QFileInfo(url.toLocalFile()).isFile()) { | 1401 | if (!QFileInfo(url.toLocalFile()).isFile()) { |
| 1402 | if (!moduleId.contains("./")) { | ||
| 1403 | // the user might be trying to refer to a relative file without anchoring it | ||
| 1404 | // let's do them a favor and test for that case -- offering specific advice if detected | ||
| 1405 | auto unanchoredUrl = resolvePath("./" + moduleId); | ||
| 1406 | if (QFileInfo(unanchoredUrl.toLocalFile()).isFile()) { | ||
| 1407 | auto msg = QString("relative module ids must be anchored; use './%1' instead") | ||
| 1408 | .arg(moduleId); | ||
| 1409 | return throwResolveError(makeError(message.arg(msg))); | ||
| 1410 | } | ||
| 1411 | } | ||
| 1410 | return throwResolveError(makeError(message.arg("system module not found"))); | 1412 | return throwResolveError(makeError(message.arg("system module not found"))); |
| 1411 | } | 1413 | } |
| 1412 | } | 1414 | } |
| @@ -1469,7 +1471,7 @@ bool ScriptEngine::registerModuleWithParent(const QScriptValue& module, const QS · 56 unmodified lines | |||
| 1469 | if (children.isArray()) { | 1471 | if (children.isArray()) { |
| 1470 | auto key = module.property("id"); | 1472 | auto key = module.property("id"); |
| 1471 | auto length = children.property("length").toInt32(); | 1473 | auto length = children.property("length").toInt32(); |
| 1472 | for (int i=0; i < length; i++) { | 1474 | for (int i = 0; i < length; i++) { |
| 1473 | if (children.property(i).property("id").strictlyEquals(key)) { | 1475 | if (children.property(i).property("id").strictlyEquals(key)) { |
| 1474 | qCDebug(scriptengine_module) << key.toString() << " updating parent.children[" << i << "] = module"; | 1476 | qCDebug(scriptengine_module) << key.toString() << " updating parent.children[" << i << "] = module"; |
| 1475 | children.setProperty(i, module); | 1477 | children.setProperty(i, module); |
| @@ -1516,10 +1518,10 @@ QScriptValue ScriptEngine::newModule(const QString& modulePath, const QScriptVal · 40 unmodified lines | |||
| 1516 | } | 1518 | } |
| 1517 | 1519 | ||
| 1518 | // synchronously fetch a module's source code using BatchLoader | 1520 | // synchronously fetch a module's source code using BatchLoader |
| 1519 | QScriptValue ScriptEngine::fetchModuleSource(const QString& modulePath, const bool forceDownload) { | 1521 | QVariantMap ScriptEngine::fetchModuleSource(const QString& modulePath, const bool forceDownload) { |
| 1520 | using UrlMap = QMap<QUrl, QString>; | 1522 | using UrlMap = QMap<QUrl, QString>; |
| 1521 | auto scriptCache = DependencyManager::get<ScriptCache>(); | 1523 | auto scriptCache = DependencyManager::get<ScriptCache>(); |
| 1522 | QScriptValue req = newObject(); | 1524 | QVariantMap req; |
| 1523 | qCDebug(scriptengine_module) << "require.fetchModuleSource: " << QUrl(modulePath).fileName() << QThread::currentThread(); | 1525 | qCDebug(scriptengine_module) << "require.fetchModuleSource: " << QUrl(modulePath).fileName() << QThread::currentThread(); |
| 1524 | 1526 | ||
| 1525 | auto onload = [=, &req](const UrlMap& data, const UrlMap& _status) { | 1527 | auto onload = [=, &req](const UrlMap& data, const UrlMap& _status) { |
| @@ -1528,13 +1530,13 @@ QScriptValue ScriptEngine::fetchModuleSource(const QString& modulePath, const bo · 2 unmodified lines | |||
| 1528 | auto contents = data[url]; | 1530 | auto contents = data[url]; |
| 1529 | qCDebug(scriptengine_module) << "require.fetchModuleSource.onload: " << QUrl(url).fileName() << status << QThread::currentThread(); | 1531 | qCDebug(scriptengine_module) << "require.fetchModuleSource.onload: " << QUrl(url).fileName() << status << QThread::currentThread(); |
| 1530 | if (isStopping()) { | 1532 | if (isStopping()) { |
| 1531 | req.setProperty("status", "Stopped"); | 1533 | req["status"] = "Stopped"; |
| 1532 | req.setProperty("success", false); | 1534 | req["success"] = false; |
| 1533 | } else { | 1535 | } else { |
| 1534 | req.setProperty("url", url); | 1536 | req["url"] = url; |
| 1535 | req.setProperty("status", status); | 1537 | req["status"] = status; |
| 1536 | req.setProperty("success", ScriptCache::isSuccessStatus(status)); | 1538 | req["success"] = ScriptCache::isSuccessStatus(status); |
| 1537 | req.setProperty("contents", contents, READONLY_HIDDEN_PROP_FLAGS); | 1539 | req["contents"] = contents; |
| 1538 | } | 1540 | } |
| 1539 | }; | 1541 | }; |
| 1540 | 1542 | ||
| @@ -1661,17 +1663,17 @@ QScriptValue ScriptEngine::require(const QString& moduleId) { · 120 unmodified lines | |||
| 1661 | // download the module source | 1663 | // download the module source |
| 1662 | auto req = fetchModuleSource(modulePath, invalidateCache); | 1664 | auto req = fetchModuleSource(modulePath, invalidateCache); |
| 1663 | 1665 | ||
| 1664 | if (!req.property("success").toBool()) { | 1666 | if (!req.contains("success") || !req["success"].toBool()) { |
| 1665 | auto error = QString("error retrieving script (%1)").arg(req.property("status").toString()); | 1667 | auto error = QString("error retrieving script (%1)").arg(req["status"].toString()); |
| 1666 | return throwModuleError(modulePath, error); | 1668 | return throwModuleError(modulePath, error); |
| 1667 | } | 1669 | } |
| 1668 | 1670 | ||
| 1669 | #if DEBUG_JS_MODULES | 1671 | #if DEBUG_JS_MODULES |
| 1670 | qCDebug(scriptengine_module) << "require.loaded: " << | 1672 | qCDebug(scriptengine_module) << "require.loaded: " << |
| 1671 | QUrl(req.property("url").toString()).fileName() << req.property("status").toString(); | 1673 | QUrl(req["url"].toString()).fileName() << req["status"].toString(); |
| 1672 | #endif | 1674 | #endif |
| 1673 | 1675 | ||
| 1674 | auto sourceCode = req.property("contents").toString(); | 1676 | auto sourceCode = req["contents"].toString(); |
| 1675 | 1677 | ||
| 1676 | if (QUrl(modulePath).fileName().endsWith(".json", Qt::CaseInsensitive)) { | 1678 | if (QUrl(modulePath).fileName().endsWith(".json", Qt::CaseInsensitive)) { |
| 1677 | module.setProperty("content-type", "application/json"); | 1679 | module.setProperty("content-type", "application/json"); |
| @@ -1679,28 +1681,6 @@ QScriptValue ScriptEngine::require(const QString& moduleId) { · 1 unmodified line | |||
| 1679 | module.setProperty("content-type", "application/javascript"); | 1681 | module.setProperty("content-type", "application/javascript"); |
| 1680 | } | 1682 | } |
| 1681 | 1683 | ||
| 1682 | { | ||
| 1683 | // It seems that many JSON sources don't actually put .json in the URL... | ||
| 1684 | // so for now as a workaround users wanting to indicate JSON parsing mode can | ||
| 1685 | // do so by calling with a custom this context, eg: | ||
| 1686 | // | ||
| 1687 | // var ITEMS_URL = 'https://highfidelity.io/api/v1/marketplace/items'; | ||
| 1688 | // var thisObject = { 'content-type': 'application/json' }; | ||
| 1689 | // var items = Script.require.call(thisObject, ITEMS_URL + '?category=everything&sort=recent'); | ||
| 1690 | |||
| 1691 | auto thisObject = currentContext()->thisObject(); | ||
| 1692 | bool calledWithCustomThis = thisObject.isObject() && | ||
| 1693 | !thisObject.strictlyEquals(globalObject()) && | ||
| 1694 | !thisObject.toQObject(); | ||
| 1695 | |||
| 1696 | if (calledWithCustomThis) { | ||
| 1697 | #ifdef DEBUG_JS | ||
| 1698 | _debugDump("this", thisObject); | ||
| 1699 | #endif | ||
| 1700 | applyUserOptions(module, thisObject); | ||
| 1701 | } | ||
| 1702 | } | ||
| 1703 | |||
| 1704 | // evaluate the module | 1684 | // evaluate the module |
| 1705 | auto result = instantiateModule(module, sourceCode); | 1685 | auto result = instantiateModule(module, sourceCode); |
| 1706 | 1686 | ||
| @@ -1721,54 +1701,6 @@ QScriptValue ScriptEngine::require(const QString& moduleId) { · 14 unmodified lines | |||
| 1721 | return module.property("exports"); | 1701 | return module.property("exports"); |
| 1722 | } | 1702 | } |
| 1723 | 1703 | ||
| 1724 | // User-configurable override options | ||
| 1725 | void ScriptEngine::applyUserOptions(QScriptValue& module, QScriptValue& options) { | ||
| 1726 | if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { | ||
| 1727 | return; | ||
| 1728 | } | ||
| 1729 | if (!options.isValid()) { | ||
| 1730 | return; | ||
| 1731 | } | ||
| 1732 | // options['content-type'] === 'application/json' | ||
| 1733 | // -- allows JSON modules to be used from URLs not ending in .json | ||
| 1734 | if (options.property("content-type").isString()) { | ||
| 1735 | module.setProperty("content-type", options.property("content-type")); | ||
| 1736 | qCDebug(scriptengine_module) << "module['content-type'] =" << module.property("content-type").toString(); | ||
| 1737 | } | ||
| 1738 | |||
| 1739 | if (ScriptEngine::_enableExtendedModuleCompatbility.get()) { | ||
| 1740 | auto closure = module.property("__closure__"); | ||
| 1741 | |||
| 1742 | auto maybeSetToExports = [&](const QString& key) { | ||
| 1743 | if (options.property(key).toString() == "exports") { | ||
| 1744 | closure.setProperty(key, module.property("exports")); | ||
| 1745 | qCDebug(scriptengine_module) << "module.closure[" << key << "] = exports"; | ||
| 1746 | } | ||
| 1747 | }; | ||
| 1748 | |||
| 1749 | // options[{key}] = 'exports' | ||
| 1750 | // several "agnostic" modules in the wild are just one step away from being compatible -- | ||
| 1751 | // they just didn't know not to look specifically for this, self or global for attaching | ||
| 1752 | // things onto. | ||
| 1753 | maybeSetToExports("global"); | ||
| 1754 | maybeSetToExports("self"); | ||
| 1755 | maybeSetToExports("this"); | ||
| 1756 | |||
| 1757 | // when options is an Object it will get used as the value of "this" during module evaluation | ||
| 1758 | // (which is what one might expect when calling require.call(thisObject, ...)) | ||
| 1759 | if (options.isObject()) { | ||
| 1760 | closure.setProperty("this", options); | ||
| 1761 | } | ||
| 1762 | |||
| 1763 | // when options.global is an Object it'll get used as the global object (during evaluation only) | ||
| 1764 | if (options.property("global").isObject()) { | ||
| 1765 | closure.setProperty("global", options.property("global")); | ||
| 1766 | qCDebug(scriptengine_module) << "module.closure['global'] = options.global"; | ||
| 1767 | } | ||
| 1768 | } | ||
| 1769 | maybeEmitUncaughtException(__FUNCTION__); | ||
| 1770 | } | ||
| 1771 | |||
| 1772 | // If a callback is specified, the included files will be loaded asynchronously and the callback will be called | 1704 | // If a callback is specified, the included files will be loaded asynchronously and the callback will be called |
| 1773 | // when all of the files have finished loading. | 1705 | // when all of the files have finished loading. |
| 1774 | // If no callback is specified, the included files will be loaded synchronously and will block execution until | 1706 | // If no callback is specified, the included files will be loaded synchronously and will block execution until |