History

* changes per CR feedback * revert JSON content-type workaround * add specific error check / advice for unanchored module ids * update unit tests

humbletim committed 9 years ago

Commit 52a57151 parent db73c805 files · +65 −128Browse files

Showing the changes to libraries/script-engine/src/ScriptEngine.cpp (1 of 5 files). Show all

Unified · Split

libraries/script-engine/src/ScriptEngine.cpp+32−100
@@ -73,15 +73,11 @@ · 72 unmodified lines
7373
7474 #include "MIDIEvent.h"
7575
76−const QString ScriptEngine::_SETTINGS_ENABLE_EXTENDED_MODULE_COMPAT {
77− "com.highfidelity.experimental.enableExtendedModuleCompatbility"
78−};
79−
8076 const QString ScriptEngine::_SETTINGS_ENABLE_EXTENDED_EXCEPTIONS {
8177 "com.highfidelity.experimental.enableExtendedJSExceptions"
8278 };
8379
84−static const int MAX_MODULE_ID_LENTGH { 4096 };
80+static const int MAX_MODULE_ID_LENGTH { 4096 };
8581 static const int MAX_DEBUG_VALUE_LENGTH { 80 };
8682
8783 static const QScriptEngine::QObjectWrapOptions DEFAULT_QOBJECT_WRAP_OPTIONS =
@@ -96,7 +92,7 @@ int functionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature · 8 unmodified lines
9692
9793 Q_LOGGING_CATEGORY(scriptengineScript, "hifi.scriptengine.script")
9894
99−static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){
95+static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) {
10096 QString message = "";
10197 for (int i = 0; i < context->argumentCount(); i++) {
10298 if (i > 0) {
@@ -345,11 +341,7 @@ void ScriptEngine::runInThread() { · 242 unmodified lines
345341 // The thread interface cannot live on itself, and we want to move this into the thread, so
346342 // the thread cannot have this as a parent.
347343 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:",""));
353345 moveToThread(workerThread);
354346
355347 // 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
550542
551543 void ScriptEngine::resetModuleCache(bool deleteScriptCache) {
552544 if (QThread::currentThread() != thread()) {
553− executeOnScriptThread([=](){ resetModuleCache(deleteScriptCache); });
545+ executeOnScriptThread([=]() { resetModuleCache(deleteScriptCache); });
554546 return;
555547 }
556548 auto jsRequire = globalObject().property("Script").property("require");
@@ -1379,14 +1371,14 @@ QString ScriptEngine::_requireResolve(const QString& moduleId, const QString& re · 822 unmodified lines
13791371 auto throwResolveError = [&](const QScriptValue& error) -> QString {
13801372 raiseException(error);
13811373 maybeEmitUncaughtException("require.resolve");
1382− return nullptr;
1374+ return QString();
13831375 };
13841376
13851377 // de-fuzz the input a little by restricting to rational sizes
13861378 auto idLength = url.toString().length();
1387− if (idLength < 1 || idLength > MAX_MODULE_ID_LENTGH) {
1379+ if (idLength < 1 || idLength > MAX_MODULE_ID_LENGTH) {
13881380 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);
13901382 return throwResolveError(makeError(message.arg(details), "RangeError"));
13911383 }
13921384
@@ -1402,11 +1394,21 @@ QString ScriptEngine::_requireResolve(const QString& moduleId, const QString& re · 9 unmodified lines
14021394 url = resolvePath(moduleId);
14031395 } else {
14041396 // 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);
14071399 url = defaultScriptsLoc;
14081400 url.setPath(systemModulePath);
14091401 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+ }
14101412 return throwResolveError(makeError(message.arg("system module not found")));
14111413 }
14121414 }
@@ -1469,7 +1471,7 @@ bool ScriptEngine::registerModuleWithParent(const QScriptValue& module, const QS · 56 unmodified lines
14691471 if (children.isArray()) {
14701472 auto key = module.property("id");
14711473 auto length = children.property("length").toInt32();
1472− for (int i=0; i < length; i++) {
1474+ for (int i = 0; i < length; i++) {
14731475 if (children.property(i).property("id").strictlyEquals(key)) {
14741476 qCDebug(scriptengine_module) << key.toString() << " updating parent.children[" << i << "] = module";
14751477 children.setProperty(i, module);
@@ -1516,10 +1518,10 @@ QScriptValue ScriptEngine::newModule(const QString& modulePath, const QScriptVal · 40 unmodified lines
15161518 }
15171519
15181520 // 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) {
15201522 using UrlMap = QMap<QUrl, QString>;
15211523 auto scriptCache = DependencyManager::get<ScriptCache>();
1522− QScriptValue req = newObject();
1524+ QVariantMap req;
15231525 qCDebug(scriptengine_module) << "require.fetchModuleSource: " << QUrl(modulePath).fileName() << QThread::currentThread();
15241526
15251527 auto onload = [=, &req](const UrlMap& data, const UrlMap& _status) {
@@ -1528,13 +1530,13 @@ QScriptValue ScriptEngine::fetchModuleSource(const QString& modulePath, const bo · 2 unmodified lines
15281530 auto contents = data[url];
15291531 qCDebug(scriptengine_module) << "require.fetchModuleSource.onload: " << QUrl(url).fileName() << status << QThread::currentThread();
15301532 if (isStopping()) {
1531− req.setProperty("status", "Stopped");
1532− req.setProperty("success", false);
1533+ req["status"] = "Stopped";
1534+ req["success"] = false;
15331535 } 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;
15381540 }
15391541 };
15401542
@@ -1661,17 +1663,17 @@ QScriptValue ScriptEngine::require(const QString& moduleId) { · 120 unmodified lines
16611663 // download the module source
16621664 auto req = fetchModuleSource(modulePath, invalidateCache);
16631665
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());
16661668 return throwModuleError(modulePath, error);
16671669 }
16681670
16691671 #if DEBUG_JS_MODULES
16701672 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();
16721674 #endif
16731675
1674− auto sourceCode = req.property("contents").toString();
1676+ auto sourceCode = req["contents"].toString();
16751677
16761678 if (QUrl(modulePath).fileName().endsWith(".json", Qt::CaseInsensitive)) {
16771679 module.setProperty("content-type", "application/json");
@@ -1679,28 +1681,6 @@ QScriptValue ScriptEngine::require(const QString& moduleId) { · 1 unmodified line
16791681 module.setProperty("content-type", "application/javascript");
16801682 }
16811683
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−
17041684 // evaluate the module
17051685 auto result = instantiateModule(module, sourceCode);
17061686
@@ -1721,54 +1701,6 @@ QScriptValue ScriptEngine::require(const QString& moduleId) { · 14 unmodified lines
17211701 return module.property("exports");
17221702 }
17231703
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−
17721704 // If a callback is specified, the included files will be loaded asynchronously and the callback will be called
17731705 // when all of the files have finished loading.
17741706 // If no callback is specified, the included files will be loaded synchronously and will block execution until