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 | 74 | #include "MIDIEvent.h" |
| 75 | 75 | |
| 76 | −const QString ScriptEngine::_SETTINGS_ENABLE_EXTENDED_MODULE_COMPAT { | |
| 77 | − "com.highfidelity.experimental.enableExtendedModuleCompatbility" | |
| 78 | −}; | |
| 79 | − | |
| 80 | 76 | const QString ScriptEngine::_SETTINGS_ENABLE_EXTENDED_EXCEPTIONS { |
| 81 | 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 | 81 | static const int MAX_DEBUG_VALUE_LENGTH { 80 }; |
| 86 | 82 | |
| 87 | 83 | static const QScriptEngine::QObjectWrapOptions DEFAULT_QOBJECT_WRAP_OPTIONS = |
| @@ -96,7 +92,7 @@ int functionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature · 8 unmodified lines | ||
| 96 | 92 | |
| 97 | 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 | 96 | QString message = ""; |
| 101 | 97 | for (int i = 0; i < context->argumentCount(); i++) { |
| 102 | 98 | if (i > 0) { |
| @@ -345,11 +341,7 @@ void ScriptEngine::runInThread() { · 242 unmodified lines | ||
| 345 | 341 | // The thread interface cannot live on itself, and we want to move this into the thread, so |
| 346 | 342 | // the thread cannot have this as a parent. |
| 347 | 343 | QThread* workerThread = new QThread(); |
| 348 | −#ifdef Q_OS_LINUX | |
| 349 | − workerThread->setObjectName(QString("js:") + getFilename()); | |
| 350 | −#else | |
| 351 | − workerThread->setObjectName(QString("Script Thread:") + getFilename()); | |
| 352 | −#endif | |
| 344 | + workerThread->setObjectName(QString("js:") + getFilename().replace("about:","")); | |
| 353 | 345 | moveToThread(workerThread); |
| 354 | 346 | |
| 355 | 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 | 543 | void ScriptEngine::resetModuleCache(bool deleteScriptCache) { |
| 552 | 544 | if (QThread::currentThread() != thread()) { |
| 553 | − executeOnScriptThread([=](){ resetModuleCache(deleteScriptCache); }); | |
| 545 | + executeOnScriptThread([=]() { resetModuleCache(deleteScriptCache); }); | |
| 554 | 546 | return; |
| 555 | 547 | } |
| 556 | 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 | 1371 | auto throwResolveError = [&](const QScriptValue& error) -> QString { |
| 1380 | 1372 | raiseException(error); |
| 1381 | 1373 | maybeEmitUncaughtException("require.resolve"); |
| 1382 | − return nullptr; | |
| 1374 | + return QString(); | |
| 1383 | 1375 | }; |
| 1384 | 1376 | |
| 1385 | 1377 | // de-fuzz the input a little by restricting to rational sizes |
| 1386 | 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 | 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 | 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 | 1394 | url = resolvePath(moduleId); |
| 1403 | 1395 | } else { |
| 1404 | 1396 | // check if the moduleId refers to a "system" module |
| 1405 | − QString defaultsPath = defaultScriptsLoc.path(); | |
| 1406 | − QString systemModulePath = QString("%1/modules/%2.js").arg(defaultsPath).arg(moduleId); | |
| 1397 | + QString systemPath = defaultScriptsLoc.path(); | |
| 1398 | + QString systemModulePath = QString("%1/modules/%2.js").arg(systemPath).arg(moduleId); | |
| 1407 | 1399 | url = defaultScriptsLoc; |
| 1408 | 1400 | url.setPath(systemModulePath); |
| 1409 | 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 | 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 | 1471 | if (children.isArray()) { |
| 1470 | 1472 | auto key = module.property("id"); |
| 1471 | 1473 | auto length = children.property("length").toInt32(); |
| 1472 | − for (int i=0; i < length; i++) { | |
| 1474 | + for (int i = 0; i < length; i++) { | |
| 1473 | 1475 | if (children.property(i).property("id").strictlyEquals(key)) { |
| 1474 | 1476 | qCDebug(scriptengine_module) << key.toString() << " updating parent.children[" << i << "] = module"; |
| 1475 | 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 | 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 | 1522 | using UrlMap = QMap<QUrl, QString>; |
| 1521 | 1523 | auto scriptCache = DependencyManager::get<ScriptCache>(); |
| 1522 | − QScriptValue req = newObject(); | |
| 1524 | + QVariantMap req; | |
| 1523 | 1525 | qCDebug(scriptengine_module) << "require.fetchModuleSource: " << QUrl(modulePath).fileName() << QThread::currentThread(); |
| 1524 | 1526 | |
| 1525 | 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 | 1530 | auto contents = data[url]; |
| 1529 | 1531 | qCDebug(scriptengine_module) << "require.fetchModuleSource.onload: " << QUrl(url).fileName() << status << QThread::currentThread(); |
| 1530 | 1532 | if (isStopping()) { |
| 1531 | − req.setProperty("status", "Stopped"); | |
| 1532 | − req.setProperty("success", false); | |
| 1533 | + req["status"] = "Stopped"; | |
| 1534 | + req["success"] = false; | |
| 1533 | 1535 | } else { |
| 1534 | − req.setProperty("url", url); | |
| 1535 | − req.setProperty("status", status); | |
| 1536 | − req.setProperty("success", ScriptCache::isSuccessStatus(status)); | |
| 1537 | − req.setProperty("contents", contents, READONLY_HIDDEN_PROP_FLAGS); | |
| 1536 | + req["url"] = url; | |
| 1537 | + req["status"] = status; | |
| 1538 | + req["success"] = ScriptCache::isSuccessStatus(status); | |
| 1539 | + req["contents"] = contents; | |
| 1538 | 1540 | } |
| 1539 | 1541 | }; |
| 1540 | 1542 | |
| @@ -1661,17 +1663,17 @@ QScriptValue ScriptEngine::require(const QString& moduleId) { · 120 unmodified lines | ||
| 1661 | 1663 | // download the module source |
| 1662 | 1664 | auto req = fetchModuleSource(modulePath, invalidateCache); |
| 1663 | 1665 | |
| 1664 | − if (!req.property("success").toBool()) { | |
| 1665 | − auto error = QString("error retrieving script (%1)").arg(req.property("status").toString()); | |
| 1666 | + if (!req.contains("success") || !req["success"].toBool()) { | |
| 1667 | + auto error = QString("error retrieving script (%1)").arg(req["status"].toString()); | |
| 1666 | 1668 | return throwModuleError(modulePath, error); |
| 1667 | 1669 | } |
| 1668 | 1670 | |
| 1669 | 1671 | #if DEBUG_JS_MODULES |
| 1670 | 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 | 1674 | #endif |
| 1673 | 1675 | |
| 1674 | − auto sourceCode = req.property("contents").toString(); | |
| 1676 | + auto sourceCode = req["contents"].toString(); | |
| 1675 | 1677 | |
| 1676 | 1678 | if (QUrl(modulePath).fileName().endsWith(".json", Qt::CaseInsensitive)) { |
| 1677 | 1679 | module.setProperty("content-type", "application/json"); |
| @@ -1679,28 +1681,6 @@ QScriptValue ScriptEngine::require(const QString& moduleId) { · 1 unmodified line | ||
| 1679 | 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 | 1684 | // evaluate the module |
| 1705 | 1685 | auto result = instantiateModule(module, sourceCode); |
| 1706 | 1686 | |
| @@ -1721,54 +1701,6 @@ QScriptValue ScriptEngine::require(const QString& moduleId) { · 14 unmodified lines | ||
| 1721 | 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 | 1704 | // If a callback is specified, the included files will be loaded asynchronously and the callback will be called |
| 1773 | 1705 | // when all of the files have finished loading. |
| 1774 | 1706 | // If no callback is specified, the included files will be loaded synchronously and will block execution until |