From d9ddcb554d5eeefd9a04527f399bdbc2c2138cbb Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Sat, 22 May 2021 23:38:35 -0700 Subject: [PATCH] INCOMPLETE COMMIT - WILL BE FORCE-PUSHED working to clean up the QtScript implementation and move towards completion of the proxy interface --- .../script-engine-qtscript/CMakeLists.txt | 10 - .../src/BaseScriptEngine.cpp | 332 ------- .../src/BaseScriptEngine.h | 99 --- .../src/ScriptEngineQtScript.cpp | 493 ----------- libraries/script-engine/CMakeLists.txt | 4 +- libraries/script-engine/src/ScriptContext.h | 11 +- libraries/script-engine/src/ScriptEngine.h | 1 + libraries/script-engine/src/ScriptManager.cpp | 65 +- libraries/script-engine/src/ScriptManager.h | 4 +- libraries/script-engine/src/ScriptValue.h | 2 + libraries/script-engine/src/Scriptable.cpp | 24 + .../src/qtscript/ArrayBufferClass.cpp | 180 ++++ .../src/qtscript}/ArrayBufferClass.h | 0 .../src/qtscript}/ArrayBufferPrototype.cpp | 3 +- .../src/qtscript}/ArrayBufferPrototype.h | 0 .../src/qtscript}/ArrayBufferViewClass.cpp | 0 .../src/qtscript}/ArrayBufferViewClass.h | 0 .../src/qtscript}/DataViewClass.cpp | 0 .../src/qtscript}/DataViewClass.h | 0 .../src/qtscript}/DataViewPrototype.cpp | 4 +- .../src/qtscript}/DataViewPrototype.h | 0 .../src/qtscript/ScriptContextQtAgent.cpp | 73 ++ .../src/qtscript/ScriptContextQtAgent.h | 47 + .../src/qtscript/ScriptContextQtWrapper.cpp | 73 ++ .../src/qtscript/ScriptContextQtWrapper.h | 48 + .../src/qtscript/ScriptEngineQtScript.cpp | 837 ++++++++++++++++++ .../src/qtscript}/ScriptEngineQtScript.h | 145 ++- .../src/qtscript/ScriptProgramQtWrapper.h | 40 + .../src/qtscript/ScriptValueQtWrapper.cpp | 196 ++++ .../src/qtscript/ScriptValueQtWrapper.h | 85 ++ .../src/qtscript}/TypedArrayPrototype.cpp | 0 .../src/qtscript}/TypedArrayPrototype.h | 0 .../src/qtscript}/TypedArrays.cpp | 0 .../src/qtscript}/TypedArrays.h | 0 34 files changed, 1751 insertions(+), 1025 deletions(-) delete mode 100644 libraries/script-engine-qtscript/CMakeLists.txt delete mode 100644 libraries/script-engine-qtscript/src/BaseScriptEngine.cpp delete mode 100644 libraries/script-engine-qtscript/src/BaseScriptEngine.h delete mode 100644 libraries/script-engine-qtscript/src/ScriptEngineQtScript.cpp create mode 100644 libraries/script-engine/src/Scriptable.cpp create mode 100644 libraries/script-engine/src/qtscript/ArrayBufferClass.cpp rename libraries/{script-engine-qtscript/src => script-engine/src/qtscript}/ArrayBufferClass.h (100%) rename libraries/{script-engine-qtscript/src => script-engine/src/qtscript}/ArrayBufferPrototype.cpp (98%) rename libraries/{script-engine-qtscript/src => script-engine/src/qtscript}/ArrayBufferPrototype.h (100%) rename libraries/{script-engine-qtscript/src => script-engine/src/qtscript}/ArrayBufferViewClass.cpp (100%) rename libraries/{script-engine-qtscript/src => script-engine/src/qtscript}/ArrayBufferViewClass.h (100%) rename libraries/{script-engine-qtscript/src => script-engine/src/qtscript}/DataViewClass.cpp (100%) rename libraries/{script-engine-qtscript/src => script-engine/src/qtscript}/DataViewClass.h (100%) rename libraries/{script-engine-qtscript/src => script-engine/src/qtscript}/DataViewPrototype.cpp (98%) rename libraries/{script-engine-qtscript/src => script-engine/src/qtscript}/DataViewPrototype.h (100%) create mode 100644 libraries/script-engine/src/qtscript/ScriptContextQtAgent.cpp create mode 100644 libraries/script-engine/src/qtscript/ScriptContextQtAgent.h create mode 100644 libraries/script-engine/src/qtscript/ScriptContextQtWrapper.cpp create mode 100644 libraries/script-engine/src/qtscript/ScriptContextQtWrapper.h create mode 100644 libraries/script-engine/src/qtscript/ScriptEngineQtScript.cpp rename libraries/{script-engine-qtscript/src => script-engine/src/qtscript}/ScriptEngineQtScript.h (69%) create mode 100644 libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.h create mode 100644 libraries/script-engine/src/qtscript/ScriptValueQtWrapper.cpp create mode 100644 libraries/script-engine/src/qtscript/ScriptValueQtWrapper.h rename libraries/{script-engine-qtscript/src => script-engine/src/qtscript}/TypedArrayPrototype.cpp (100%) rename libraries/{script-engine-qtscript/src => script-engine/src/qtscript}/TypedArrayPrototype.h (100%) rename libraries/{script-engine-qtscript/src => script-engine/src/qtscript}/TypedArrays.cpp (100%) rename libraries/{script-engine-qtscript/src => script-engine/src/qtscript}/TypedArrays.h (100%) diff --git a/libraries/script-engine-qtscript/CMakeLists.txt b/libraries/script-engine-qtscript/CMakeLists.txt deleted file mode 100644 index 65a80d0ff34..00000000000 --- a/libraries/script-engine-qtscript/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -set(TARGET_NAME script-engine-qtscript) -# FIXME Move undo scripting interface to application and remove Widgets -setup_hifi_library(Gui Script ScriptTools) - -link_hifi_libraries() -include_hifi_library_headers(animation) -include_hifi_library_headers(entities) -include_hifi_library_headers(networking) -include_hifi_library_headers(shared) -include_hifi_library_headers(script-engine) \ No newline at end of file diff --git a/libraries/script-engine-qtscript/src/BaseScriptEngine.cpp b/libraries/script-engine-qtscript/src/BaseScriptEngine.cpp deleted file mode 100644 index 6e815174735..00000000000 --- a/libraries/script-engine-qtscript/src/BaseScriptEngine.cpp +++ /dev/null @@ -1,332 +0,0 @@ -// -// BaseScriptEngine.cpp -// libraries/script-engine/src -// -// Created by Timothy Dedischew on 02/01/17. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "BaseScriptEngine.h" -#include "SharedLogging.h" - -#include -#include -#include -#include -#include -#include - -#include "Profile.h" - -bool BaseScriptEngine::IS_THREADSAFE_INVOCATION(const QThread *thread, const QString& method) { - if (QThread::currentThread() == thread) { - return true; - } - qCCritical(shared) << QString("Scripting::%1 @ %2 -- ignoring thread-unsafe call from %3") - .arg(method).arg(thread ? thread->objectName() : "(!thread)").arg(QThread::currentThread()->objectName()); - qCDebug(shared) << "(please resolve on the calling side by using invokeMethod, executeOnScriptThread, etc.)"; - Q_ASSERT(false); - return false; -} - -// engine-aware JS Error copier and factory -QScriptValue BaseScriptEngine::makeError(const QScriptValue& _other, const QString& type) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return unboundNullValue(); - } - auto other = _other; - if (other.isString()) { - other = newObject(); - other.setProperty("message", _other.toString()); - } - auto proto = globalObject().property(type); - if (!proto.isFunction()) { - proto = globalObject().property(other.prototype().property("constructor").property("name").toString()); - } - if (!proto.isFunction()) { -#ifdef DEBUG_JS_EXCEPTIONS - qCDebug(shared) << "BaseScriptEngine::makeError -- couldn't find constructor for" << type << " -- using Error instead"; -#endif - proto = globalObject().property("Error"); - } - if (other.engine() != this) { - // JS Objects are parented to a specific script engine instance - // -- this effectively ~clones it locally by routing through a QVariant and back - other = toScriptValue(other.toVariant()); - } - // ~ var err = new Error(other.message) - auto err = proto.construct(QScriptValueList({other.property("message")})); - - // transfer over any existing properties - QScriptValueIterator it(other); - while (it.hasNext()) { - it.next(); - err.setProperty(it.name(), it.value()); - } - return err; -} - -// check syntax and when there are issues returns an actual "SyntaxError" with the details -QScriptValue BaseScriptEngine::lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return unboundNullValue(); - } - const auto syntaxCheck = checkSyntax(sourceCode); - if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { - auto err = globalObject().property("SyntaxError") - .construct(QScriptValueList({syntaxCheck.errorMessage()})); - err.setProperty("fileName", fileName); - err.setProperty("lineNumber", syntaxCheck.errorLineNumber()); - err.setProperty("expressionBeginOffset", syntaxCheck.errorColumnNumber()); - err.setProperty("stack", currentContext()->backtrace().join(ScriptManager::SCRIPT_BACKTRACE_SEP)); - { - const auto error = syntaxCheck.errorMessage(); - const auto line = QString::number(syntaxCheck.errorLineNumber()); - const auto column = QString::number(syntaxCheck.errorColumnNumber()); - // for compatibility with legacy reporting - const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, fileName, line, column); - err.setProperty("formatted", message); - } - return err; - } - return QScriptValue(); -} - -// this pulls from the best available information to create a detailed snapshot of the current exception -QScriptValue BaseScriptEngine::cloneUncaughtException(const QString& extraDetail) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return unboundNullValue(); - } - if (!hasUncaughtException()) { - return unboundNullValue(); - } - auto exception = uncaughtException(); - // ensure the error object is engine-local - auto err = makeError(exception); - - // not sure why Qt does't offer uncaughtExceptionFileName -- but the line number - // on its own is often useless/wrong if arbitrarily married to a filename. - // when the error object already has this info, it seems to be the most reliable - auto fileName = exception.property("fileName").toString(); - auto lineNumber = exception.property("lineNumber").toInt32(); - - // the backtrace, on the other hand, seems most reliable taken from uncaughtExceptionBacktrace - auto backtrace = uncaughtExceptionBacktrace(); - if (backtrace.isEmpty()) { - // fallback to the error object - backtrace = exception.property("stack").toString().split(ScriptManager::SCRIPT_BACKTRACE_SEP); - } - // the ad hoc "detail" property can be used now to embed additional clues - auto detail = exception.property("detail").toString(); - if (detail.isEmpty()) { - detail = extraDetail; - } else if (!extraDetail.isEmpty()) { - detail += "(" + extraDetail + ")"; - } - if (lineNumber <= 0) { - lineNumber = uncaughtExceptionLineNumber(); - } - if (fileName.isEmpty()) { - // climb the stack frames looking for something useful to display - for (auto c = currentContext(); c && fileName.isEmpty(); c = c->parentContext()) { - QScriptContextInfo info { c }; - if (!info.fileName().isEmpty()) { - // take fileName:lineNumber as a pair - fileName = info.fileName(); - lineNumber = info.lineNumber(); - if (backtrace.isEmpty()) { - backtrace = c->backtrace(); - } - break; - } - } - } - err.setProperty("fileName", fileName); - err.setProperty("lineNumber", lineNumber ); - err.setProperty("detail", detail); - err.setProperty("stack", backtrace.join(ScriptManager::SCRIPT_BACKTRACE_SEP)); - -#ifdef DEBUG_JS_EXCEPTIONS - err.setProperty("_fileName", exception.property("fileName").toString()); - err.setProperty("_stack", uncaughtExceptionBacktrace().join(SCRIPT_BACKTRACE_SEP)); - err.setProperty("_lineNumber", uncaughtExceptionLineNumber()); -#endif - return err; -} - -bool BaseScriptEngine::raiseException(const QScriptValue& exception) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return false; - } - if (currentContext()) { - // we have an active context / JS stack frame so throw the exception per usual - currentContext()->throwValue(makeError(exception)); - return true; - } else { - // we are within a pure C++ stack frame (ie: being called directly by other C++ code) - // in this case no context information is available so just emit the exception for reporting - emit unhandledException(makeError(exception)); - } - return false; -} - -bool BaseScriptEngine::maybeEmitUncaughtException(const QString& debugHint) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return false; - } - if (!isEvaluating() && hasUncaughtException()) { - emit unhandledException(cloneUncaughtException(debugHint)); - clearExceptions(); - return true; - } - return false; -} - -QScriptValue BaseScriptEngine::evaluateInClosure(const QScriptValue& closure, const QScriptProgram& program) { - PROFILE_RANGE(script, "evaluateInClosure"); - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return unboundNullValue(); - } - const auto fileName = program.fileName(); - const auto shortName = QUrl(fileName).fileName(); - - QScriptValue result; - QScriptValue oldGlobal; - auto global = closure.property("global"); - if (global.isObject()) { -#ifdef DEBUG_JS - qCDebug(shared) << " setting global = closure.global" << shortName; -#endif - oldGlobal = globalObject(); - setGlobalObject(global); - } - - auto context = pushContext(); - - auto thiz = closure.property("this"); - if (thiz.isObject()) { -#ifdef DEBUG_JS - qCDebug(shared) << " setting this = closure.this" << shortName; -#endif - context->setThisObject(thiz); - } - - context->pushScope(closure); -#ifdef DEBUG_JS - qCDebug(shared) << QString("[%1] evaluateInClosure %2").arg(isEvaluating()).arg(shortName); -#endif - { - result = BaseScriptEngine::evaluate(program); - if (hasUncaughtException()) { - auto err = cloneUncaughtException(__FUNCTION__); -#ifdef DEBUG_JS_EXCEPTIONS - qCWarning(shared) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString(); - err.setProperty("_result", result); -#endif - result = err; - } - } -#ifdef DEBUG_JS - qCDebug(shared) << QString("[%1] //evaluateInClosure %2").arg(isEvaluating()).arg(shortName); -#endif - popContext(); - - if (oldGlobal.isValid()) { -#ifdef DEBUG_JS - qCDebug(shared) << " restoring global" << shortName; -#endif - setGlobalObject(oldGlobal); - } - - return result; -} - -// Lambda -QScriptValue BaseScriptEngine::newLambdaFunction(std::function operation, const QScriptValue& data, const QScriptEngine::ValueOwnership& ownership) { - auto lambda = new Lambda(this, operation, data); - auto object = newQObject(lambda, ownership); - auto call = object.property("call"); - call.setPrototype(object); // context->callee().prototype() === Lambda QObject - call.setData(data); // context->callee().data() will === data param - return call; -} -QString Lambda::toString() const { - return QString("[Lambda%1]").arg(data.isValid() ? " " + data.toString() : data.toString()); -} - -Lambda::~Lambda() { -#ifdef DEBUG_JS_LAMBDA_FUNCS - qDebug() << "~Lambda" << "this" << this; -#endif -} - -Lambda::Lambda(QScriptEngine *engine, std::function operation, QScriptValue data) - : engine(engine), operation(operation), data(data) { -#ifdef DEBUG_JS_LAMBDA_FUNCS - qDebug() << "Lambda" << data.toString(); -#endif -} -QScriptValue Lambda::call() { - if (!BaseScriptEngine::IS_THREADSAFE_INVOCATION(engine->thread(), __FUNCTION__)) { - return BaseScriptEngine::unboundNullValue(); - } - return operation(engine->currentContext(), engine); -} - -QScriptValue makeScopedHandlerObject(QScriptValue scopeOrCallback, QScriptValue methodOrName) { - auto engine = scopeOrCallback.engine(); - if (!engine) { - return scopeOrCallback; - } - auto scope = QScriptValue(); - auto callback = scopeOrCallback; - if (scopeOrCallback.isObject()) { - if (methodOrName.isString()) { - scope = scopeOrCallback; - callback = scope.property(methodOrName.toString()); - } else if (methodOrName.isFunction()) { - scope = scopeOrCallback; - callback = methodOrName; - } else if (!methodOrName.isValid()) { - // instantiate from an existing scoped handler object - if (scopeOrCallback.property("callback").isFunction()) { - scope = scopeOrCallback.property("scope"); - callback = scopeOrCallback.property("callback"); - } - } - } - auto handler = engine->newObject(); - handler.setProperty("scope", scope); - handler.setProperty("callback", callback); - return handler; -} - -QScriptValue callScopedHandlerObject(QScriptValue handler, QScriptValue err, QScriptValue result) { - return handler.property("callback").call(handler.property("scope"), QScriptValueList({ err, result })); -} - -#ifdef DEBUG_JS -void BaseScriptEngine::_debugDump(const QString& header, const QScriptValue& object, const QString& footer) { - if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { - return; - } - if (!header.isEmpty()) { - qCDebug(shared) << header; - } - if (!object.isObject()) { - qCDebug(shared) << "(!isObject)" << object.toVariant().toString() << object.toString(); - return; - } - QScriptValueIterator it(object); - while (it.hasNext()) { - it.next(); - qCDebug(shared) << it.name() << ":" << it.value().toString(); - } - if (!footer.isEmpty()) { - qCDebug(shared) << footer; - } -} -#endif diff --git a/libraries/script-engine-qtscript/src/BaseScriptEngine.h b/libraries/script-engine-qtscript/src/BaseScriptEngine.h deleted file mode 100644 index 9e78c1239cd..00000000000 --- a/libraries/script-engine-qtscript/src/BaseScriptEngine.h +++ /dev/null @@ -1,99 +0,0 @@ -// -// BaseScriptEngine.h -// libraries/script-engine/src -// -// Created by Timothy Dedischew on 02/01/17. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_BaseScriptEngine_h -#define hifi_BaseScriptEngine_h - -#include -#include -#include - -class ScriptEngineQtScript; -using ScriptEngineQtScriptPointer = QSharedPointer; - -// common base class for extending QScriptEngine itself -class BaseScriptEngine : public QScriptEngine, public QEnableSharedFromThis { - Q_OBJECT -public: - // threadsafe "unbound" version of QScriptEngine::nullValue() - static const QScriptValue unboundNullValue() { return QScriptValue(0, QScriptValue::NullValue); } - - BaseScriptEngine() {} - - /*@jsdoc - * @function Script.lintScript - * @param {string} sourceCode - Source code. - * @param {string} fileName - File name. - * @param {number} [lineNumber=1] - Line number. - * @returns {object} Object. - * @deprecated This function is deprecated and will be removed. - */ - Q_INVOKABLE QScriptValue lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1); - - /*@jsdoc - * @function Script.makeError - * @param {object} [other] - Other. - * @param {string} [type="Error"] - Error. - * @returns {object} Object. - * @deprecated This function is deprecated and will be removed. - */ - Q_INVOKABLE QScriptValue makeError(const QScriptValue& other = QScriptValue(), const QString& type = "Error"); - - QScriptValue cloneUncaughtException(const QString& detail = QString()); - QScriptValue evaluateInClosure(const QScriptValue& locals, const QScriptProgram& program); - - // if there is a pending exception and we are at the top level (non-recursive) stack frame, this emits and resets it - bool maybeEmitUncaughtException(const QString& debugHint = QString()); - - // if the currentContext() is valid then throw the passed exception; otherwise, immediately emit it. - // note: this is used in cases where C++ code might call into JS API methods directly - bool raiseException(const QScriptValue& exception); - - // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways - static bool IS_THREADSAFE_INVOCATION(const QThread *thread, const QString& method); -signals: - /*@jsdoc - * @function Script.signalHandlerException - * @param {object} exception - Exception. - * @returns {Signal} - * @deprecated This signal is deprecated and will be removed. - */ - // Script.signalHandlerException is exposed by QScriptEngine. - -protected: - // like `newFunction`, but allows mapping inline C++ lambdas with captures as callable QScriptValues - // even though the context/engine parameters are redundant in most cases, the function signature matches `newFunction` - // anyway so that newLambdaFunction can be used to rapidly prototype / test utility APIs and then if becoming - // permanent more easily promoted into regular static newFunction scenarios. - QScriptValue newLambdaFunction(std::function operation, const QScriptValue& data = QScriptValue(), const QScriptEngine::ValueOwnership& ownership = QScriptEngine::AutoOwnership); - -#ifdef DEBUG_JS - static void _debugDump(const QString& header, const QScriptValue& object, const QString& footer = QString()); -#endif -}; - -// Lambda helps create callable QScriptValues out of std::functions: -// (just meant for use from within the script engine itself) -class Lambda : public QObject { - Q_OBJECT -public: - Lambda(QScriptEngine *engine, std::function operation, QScriptValue data); - ~Lambda(); - public slots: - QScriptValue call(); - QString toString() const; -private: - QScriptEngine* engine; - std::function operation; - QScriptValue data; -}; - -#endif // hifi_BaseScriptEngine_h diff --git a/libraries/script-engine-qtscript/src/ScriptEngineQtScript.cpp b/libraries/script-engine-qtscript/src/ScriptEngineQtScript.cpp deleted file mode 100644 index ee5d0ec8a75..00000000000 --- a/libraries/script-engine-qtscript/src/ScriptEngineQtScript.cpp +++ /dev/null @@ -1,493 +0,0 @@ -// -// ScriptEngineQtScript.cpp -// libraries/script-engine-qtscript/src -// -// Created by Brad Hefta-Gaub on 12/14/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "ScriptEngineQtScript.h" - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include - -#include -#include -#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include - -//#include -//#include -#include - -//#include "ArrayBufferViewClass.h" -//#include "AssetScriptingInterface.h" -//#include "BatchLoader.h" -//#include "BaseScriptEngine.h" -//#include "DataViewClass.h" -//#include "EventTypes.h" -//#include "FileScriptingInterface.h" // unzip project -//#include "MenuItemProperties.h" -//#include "ScriptAudioInjector.h" -//#include "ScriptAvatarData.h" -//#include "ScriptCache.h" -//#include "TypedArrays.h" -//#include "XMLHttpRequestClass.h" -//#include "WebSocketClass.h" -//#include "RecordingScriptingInterface.h" -//#include "ScriptEngines.h" -//#include "StackTestScriptingInterface.h" -//#include "ModelScriptingInterface.h" - -//#include - -//#include "../../midi/src/Midi.h" // FIXME why won't a simpler include work? -//#include "MIDIEvent.h" - -//#include "SettingHandle.h" -//#include -//#include -//#include - -static const int MAX_MODULE_ID_LENGTH { 4096 }; -static const int MAX_DEBUG_VALUE_LENGTH { 80 }; - -static const QScriptEngine::QObjectWrapOptions DEFAULT_QOBJECT_WRAP_OPTIONS = - QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects; -static const QScriptValue::PropertyFlags READONLY_PROP_FLAGS { QScriptValue::ReadOnly | QScriptValue::Undeletable }; -static const QScriptValue::PropertyFlags READONLY_HIDDEN_PROP_FLAGS { READONLY_PROP_FLAGS | QScriptValue::SkipInEnumeration }; - -static const bool HIFI_AUTOREFRESH_FILE_SCRIPTS { true }; - -Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature) -int functionSignatureMetaID = qRegisterMetaType(); - -int scriptEnginePointerMetaID = qRegisterMetaType(); - -static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) { - // assemble the message by concatenating our arguments - QString message = ""; - for (int i = 0; i < context->argumentCount(); i++) { - if (i > 0) { - message += " "; - } - message += context->argument(i).toString(); - } - - // was this generated by a script engine? If we don't recognize it then send the message and exit - ScriptEngineQtScript* scriptEngine = qobject_cast(engine); - if (!scriptEngine) { - qCDebug(scriptengine_script, "%s", qUtf8Printable(message)); - return QScriptValue(); - } - - QString filename; - auto scriptManager = scriptEngine->manager(); - if (scriptManager) { - filename = scriptManager->getFilename(); - } - - // This message was sent by one of our script engines, let's try to see if we can find the source. - // Note that the first entry in the backtrace should be "print" and is somewhat useless to us - AbstractLoggerInterface* loggerInterface = AbstractLoggerInterface::get(); - if (loggerInterface && loggerInterface->showSourceDebugging()) { - QScriptContext* userContext = context; - while (userContext && QScriptContextInfo(userContext).functionType() == QScriptContextInfo::NativeFunction) { - userContext = userContext->parentContext(); - } - QString location; - if (userContext) { - QScriptContextInfo contextInfo(userContext); - QString fileName = contextInfo.fileName(); - int lineNumber = contextInfo.lineNumber(); - QString functionName = contextInfo.functionName(); - - location = functionName; - if (!fileName.isEmpty()) { - if (location.isEmpty()) { - location = fileName; - } else { - location = QString("%1 at %2").arg(location).arg(fileName); - } - } - if (lineNumber != -1) { - location = QString("%1:%2").arg(location).arg(lineNumber); - } - } - if (location.isEmpty()) { - location = filename; - } - - // give the script engine a chance to notify the system about this message - scriptEngine->print(message); - - // send the message to debug log - qCDebug(scriptengine_script, "[%s] %s", qUtf8Printable(location), qUtf8Printable(message)); - } else { - scriptEngine->print(message); - // prefix the script engine name to help disambiguate messages in the main debug log - qCDebug(scriptengine_script, "[%s] %s", qUtf8Printable(filename), qUtf8Printable(message)); - } - - return QScriptValue(); -} - -ScriptEngineQtScript::ScriptEngineQtScript(ScriptManager* scriptManager) : - BaseScriptEngine(), - _manager(scriptManager), - _arrayBufferClass(new ArrayBufferClass(this)) -{ - if (_manager) { - connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) { - if (Base::hasUncaughtException()) { - // the engine's uncaughtException() seems to produce much better stack traces here - emit _manager->unhandledException(Base::cloneUncaughtException("signalHandlerException")); - Base::clearExceptions(); - } else { - // ... but may not always be available -- so if needed we fallback to the passed exception - emit _manager->unhandledException(exception); - } - }, Qt::DirectConnection); - } - - setProcessEventsInterval(MSECS_PER_SECOND); -} - -bool ScriptEngineQtScript::isDebugMode() const { -#if defined(DEBUG) - return true; -#else - return false; -#endif -} - -ScriptEngineQtScript::~ScriptEngineQtScript() {} - -void ScriptEngineQtScript::disconnectNonEssentialSignals() { - disconnect(); - QThread* workerThread; - // Ensure the thread should be running, and does exist - if (_isRunning && _isThreaded && (workerThread = Base::thread())) { - connect(this, &QObject::destroyed, workerThread, &QThread::quit); - connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); - } -} - -void ScriptEngineQtScript::executeOnScriptThread(std::function function, const Qt::ConnectionType& type ) { - if (QThread::currentThread() != Base::thread()) { - QMetaObject::invokeMethod(this, "executeOnScriptThread", type, Q_ARG(std::function, function)); - return; - } - - function(); -} - -void ScriptEngineQtScript::registerValue(const QString& valueName, QScriptValue value) { - if (QThread::currentThread() != Base::thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerValue() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; -#endif - QMetaObject::invokeMethod(this, "registerValue", - Q_ARG(const QString&, valueName), - Q_ARG(QScriptValue, value)); - return; - } - - QStringList pathToValue = valueName.split("."); - int partsToGo = pathToValue.length(); - QScriptValue partObject = globalObject(); - - for (const auto& pathPart : pathToValue) { - partsToGo--; - if (!partObject.property(pathPart).isValid()) { - if (partsToGo > 0) { - //QObject *object = new QObject; - QScriptValue partValue = newArray(); //newQObject(object, QScriptEngine::ScriptOwnership); - partObject.setProperty(pathPart, partValue); - } else { - partObject.setProperty(pathPart, value); - } - } - partObject = partObject.property(pathPart); - } -} - -void ScriptEngineQtScript::registerGlobalObject(const QString& name, QObject* object) { - if (QThread::currentThread() != Base::thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerGlobalObject() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name; -#endif - QMetaObject::invokeMethod(this, "registerGlobalObject", - Q_ARG(const QString&, name), - Q_ARG(QObject*, object)); - return; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngineQtScript::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name; -#endif - - if (!Base::globalObject().property(name).isValid()) { - if (object) { - QScriptValue value = Base::newQObject(object, QScriptEngine::QtOwnership, DEFAULT_QOBJECT_WRAP_OPTIONS); - Base::globalObject().setProperty(name, value); - } else { - Base::globalObject().setProperty(name, QScriptValue()); - } - } -} - -void ScriptEngineQtScript::registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { - if (QThread::currentThread() != Base::thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name; -#endif - QMetaObject::invokeMethod(this, "registerFunction", - Q_ARG(const QString&, name), - Q_ARG(QScriptEngine::FunctionSignature, functionSignature), - Q_ARG(int, numArguments)); - return; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name; -#endif - - QScriptValue scriptFun = Base::newFunction(functionSignature, numArguments); - Base::globalObject().setProperty(name, scriptFun); -} - -void ScriptEngineQtScript::registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { - if (QThread::currentThread() != Base::thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] parent:" << parent << "name:" << name; -#endif - QMetaObject::invokeMethod(this, "registerFunction", - Q_ARG(const QString&, name), - Q_ARG(QScriptEngine::FunctionSignature, functionSignature), - Q_ARG(int, numArguments)); - return; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name; -#endif - - QScriptValue object = Base::globalObject().property(parent); - if (object.isValid()) { - QScriptValue scriptFun = Base::newFunction(functionSignature, numArguments); - object.setProperty(name, scriptFun); - } -} - -void ScriptEngineQtScript::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, - QScriptEngine::FunctionSignature setter, const QString& parent) { - if (QThread::currentThread() != Base::thread()) { -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerGetterSetter() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - " name:" << name << "parent:" << parent; -#endif - QMetaObject::invokeMethod(this, "registerGetterSetter", - Q_ARG(const QString&, name), - Q_ARG(QScriptEngine::FunctionSignature, getter), - Q_ARG(QScriptEngine::FunctionSignature, setter), - Q_ARG(const QString&, parent)); - return; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngineQtScript::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent; -#endif - - QScriptValue setterFunction = Base::newFunction(setter, 1); - QScriptValue getterFunction = Base::newFunction(getter); - - if (!parent.isNull() && !parent.isEmpty()) { - QScriptValue object = Base::globalObject().property(parent); - if (object.isValid()) { - object.setProperty(name, setterFunction, QScriptValue::PropertySetter); - object.setProperty(name, getterFunction, QScriptValue::PropertyGetter); - } - } else { - Base::globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter); - Base::globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter); - } -} - -// this is not redundant -- the version in BaseScriptEngine is specifically not Q_INVOKABLE -QScriptValue ScriptEngineQtScript::evaluateInClosure(const QScriptValue& closure, const QScriptProgram& program) { - return BaseScriptEngine::evaluateInClosure(closure, program); -} - -QScriptValue ScriptEngineQtScript::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) { - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { - return QScriptValue(); // bail early - } - - if (QThread::currentThread() != Base::thread()) { - QScriptValue result; -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - "sourceCode:" << sourceCode << " fileName:" << fileName << "lineNumber:" << lineNumber; -#endif - BLOCKING_INVOKE_METHOD(this, "evaluate", - Q_RETURN_ARG(QScriptValue, result), - Q_ARG(const QString&, sourceCode), - Q_ARG(const QString&, fileName), - Q_ARG(int, lineNumber)); - return result; - } - - // Check syntax - auto syntaxError = Base::lintScript(sourceCode, fileName); - if (syntaxError.isError()) { - if (!Base::isEvaluating()) { - syntaxError.setProperty("detail", "evaluate"); - } - Base::raiseException(syntaxError); - Base::maybeEmitUncaughtException("lint"); - return syntaxError; - } - QScriptProgram program { sourceCode, fileName, lineNumber }; - if (program.isNull()) { - // can this happen? - auto err = Base::makeError("could not create QScriptProgram for " + fileName); - Base::raiseException(err); - Base::maybeEmitUncaughtException("compile"); - return err; - } - - QScriptValue result; - { - result = BaseScriptEngine::evaluate(program); - Base::maybeEmitUncaughtException("evaluate"); - } - return result; -} - -void ScriptEngineQtScript::updateMemoryCost(const qint64& deltaSize) { - if (deltaSize > 0) { - // We've patched qt to fix https://highfidelity.atlassian.net/browse/BUGZ-46 on mac and windows only. -#if defined(Q_OS_WIN) || defined(Q_OS_MAC) - reportAdditionalMemoryCost(deltaSize); -#endif - } -} - -void ScriptEngineQtScript::print(const QString& message) { - QString filename; - auto scriptManager = manager(); - if (scriptManager) { - filename = scriptManager->getFilename(); - } - - emit printedMessage(message, filename); -} - - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ScriptEngine implementation -/* -ScriptValuePointer ScriptEngineQtScript::globalObject() const { - return Base::globalObject(); -} - -ScriptManager* ScriptEngineQtScript::manager() const { -} - -ScriptValuePointer ScriptEngineQtScript::newArray(uint length) { - return Base::newArray(length); -} - -ScriptValuePointer ScriptEngineQtScript::newArrayBuffer(const QByteArray& message) { - QScriptValue data = Base::newVariant(QVariant::fromValue(message)); - QScriptValue ctor = Base::globalObject().property("ArrayBuffer"); - auto array = qscriptvalue_cast(ctor.data()); - if (!array) { - return undefinedValue() - } - return Base::newObject(array, data); -} - -ScriptValuePointer ScriptEngineQtScript::newObject() { - return Base::newObject(); -} - -ScriptProgramPointer ScriptEngineQtScript::newProgram(const QString& sourceCode, const QString& fileName) { -} - -ScriptValuePointer ScriptEngineQtScript::newQObject(QObject* obj) { -} - -ScriptValuePointer ScriptEngineQtScript::newValue(bool value) { -} - -ScriptValuePointer ScriptEngineQtScript::newValue(int value) { -} - -ScriptValuePointer ScriptEngineQtScript::newValue(uint value) { -} - -ScriptValuePointer ScriptEngineQtScript::newValue(double value) { -} - -ScriptValuePointer ScriptEngineQtScript::newValue(const QString& value) { -} - -ScriptValuePointer ScriptEngineQtScript::newValue(const QLatin1String& value) { -} - -ScriptValuePointer ScriptEngineQtScript::newValue(const char* value) { -} - -ScriptValuePointer ScriptEngineQtScript::newVariant(const QVariant& value) { -} - -ScriptValuePointer ScriptEngineQtScript::nullValue() { - return Base::nullValue(); -} - -void ScriptEngineQtScript::setDefaultPrototype(int metaTypeId, const ScriptValuePointer& prototype) { -} - -ScriptValuePointer ScriptEngineQtScript::undefinedValue() { - return Base::undefinedValue(); -} -*/ \ No newline at end of file diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index f26ce434822..ae4d16db52c 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -1,13 +1,13 @@ set(TARGET_NAME script-engine) # FIXME Move undo scripting interface to application and remove Widgets -setup_hifi_library(Network WebSockets) +setup_hifi_library(Gui Network Script ScriptTools WebSockets) target_zlib() if (NOT ANDROID) target_quazip() endif () -link_hifi_libraries(script-engine-qtscript shaders) +link_hifi_libraries(shaders) include_hifi_library_headers(animation) include_hifi_library_headers(audio) include_hifi_library_headers(avatars) diff --git a/libraries/script-engine/src/ScriptContext.h b/libraries/script-engine/src/ScriptContext.h index c3bec123c4e..dbdc6a5e370 100644 --- a/libraries/script-engine/src/ScriptContext.h +++ b/libraries/script-engine/src/ScriptContext.h @@ -14,11 +14,16 @@ #include #include +#include +class ScriptContext; class ScriptEngine; +class ScriptFunctionContext; class ScriptValue; -using ScriptValuePointer = QSharedPointer; +using ScriptContextPointer = QSharedPointer; +using ScriptFunctionContextPointer = QSharedPointer; using ScriptEnginePointer = QSharedPointer; +using ScriptValuePointer = QSharedPointer; class ScriptFunctionContext { public: @@ -43,8 +48,8 @@ class ScriptContext { virtual QStringList backtrace() const = 0; virtual ScriptValuePointer callee() const = 0; virtual ScriptEnginePointer engine() const = 0; - virtual ScriptFunctionContext* functionContext() const = 0; - virtual ScriptContext* parentContext() const = 0; + virtual ScriptFunctionContextPointer functionContext() const = 0; + virtual ScriptContextPointer parentContext() const = 0; virtual ScriptValuePointer thisObject() const = 0; virtual ScriptValuePointer throwError(const QString& text) = 0; virtual ScriptValuePointer throwValue(const ScriptValuePointer& value) = 0; diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 7144c91a374..2fd92343d7e 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -103,6 +103,7 @@ class ScriptEngine { virtual ScriptValuePointer uncaughtException() const = 0; virtual QStringList uncaughtExceptionBacktrace() const = 0; virtual int uncaughtExceptionLineNumber() const = 0; + virtual void updateMemoryCost(const qint64& deltaSize) = 0; public: // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways diff --git a/libraries/script-engine/src/ScriptManager.cpp b/libraries/script-engine/src/ScriptManager.cpp index a0f7fe9ee3a..065997031f1 100644 --- a/libraries/script-engine/src/ScriptManager.cpp +++ b/libraries/script-engine/src/ScriptManager.cpp @@ -53,11 +53,9 @@ #include #include -#include "ArrayBufferViewClass.h" #include "AudioScriptingInterface.h" #include "AssetScriptingInterface.h" #include "BatchLoader.h" -#include "DataViewClass.h" #include "EventTypes.h" #include "FileScriptingInterface.h" // unzip project #include "MenuItemProperties.h" @@ -67,7 +65,6 @@ #include "ScriptContext.h" #include "ScriptEngineCast.h" #include "ScriptEngineLogging.h" -#include "TypedArrays.h" #include "XMLHttpRequestClass.h" #include "WebSocketClass.h" #include "RecordingScriptingInterface.h" @@ -133,8 +130,10 @@ static ScriptValuePointer debugPrint(ScriptContext* context, ScriptEngine* engin AbstractLoggerInterface* loggerInterface = AbstractLoggerInterface::get(); if (loggerInterface && loggerInterface->showSourceDebugging()) { ScriptContext* userContext = context; + ScriptContextPointer parentContext; // using this variable to maintain parent variable lifespan while (userContext && userContext->functionContext()->functionType() == ScriptFunctionContext::NativeFunction) { - userContext = userContext->parentContext(); + parentContext = userContext->parentContext(); + userContext = parentContext.data(); } QString location; if (userContext) { @@ -323,7 +322,7 @@ void ScriptManager::disconnectNonEssentialSignals() { connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); } } - +/* void ScriptManager::runDebuggable() { static QMenuBar* menuBar{ nullptr }; static QMenu* scriptDebugMenu { nullptr }; @@ -412,8 +411,7 @@ void ScriptManager::runDebuggable() { timer->start(10); } - - +*/ void ScriptManager::runInThread() { Q_ASSERT_X(!_isThreaded, "ScriptManager::runInThread()", "runInThread should not be called more than once"); @@ -656,9 +654,10 @@ static ScriptValuePointer scriptableResourceToScriptValue(ScriptEngine* engine, // in that case it would be too difficult to tell which one should track the memory, and // this serves the common case (use in a single script). auto data = resource->getResource(); - if (data && !resource->isInScript()) { + auto manager = engine->manager(); + if (data && manager && !resource->isInScript()) { resource->setInScript(true); - QObject::connect(data.data(), SIGNAL(updateSize(qint64)), engine, SLOT(updateMemoryCost(qint64))); + QObject::connect(data.data(), SIGNAL(updateSize(qint64)), manager, SLOT(updateMemoryCost(qint64))); } auto object = engine->newQObject( @@ -1059,6 +1058,11 @@ void ScriptManager::addEventHandler(const EntityItemID& entityID, const QString& handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler(). } +bool ScriptManager::isStopped() const { + QSharedPointer scriptEngines(_scriptEngines); + return !scriptEngines || scriptEngines->isStopped(); +} + void ScriptManager::run() { if (QThread::currentThread() != qApp->thread() && _context == Context::CLIENT_SCRIPT) { // Flag that we're allowed to access local HTML files on UI created from C++ calls on this thread @@ -1070,8 +1074,7 @@ void ScriptManager::run() { auto name = filenameParts.size() > 0 ? filenameParts[filenameParts.size() - 1] : "unknown"; PROFILE_SET_THREAD_NAME("Script: " + name); - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { + if (isStopped()) { return; // bail early - avoid setting state in init(), as evaluate() will bail too } @@ -1324,21 +1327,13 @@ void ScriptManager::callAnimationStateHandler(ScriptValuePointer callback, AnimV } void ScriptManager::updateMemoryCost(const qint64& deltaSize) { - if (deltaSize > 0) { - // We've patched qt to fix https://highfidelity.atlassian.net/browse/BUGZ-46 on mac and windows only. -#if defined(Q_OS_WIN) || defined(Q_OS_MAC) - reportAdditionalMemoryCost(deltaSize); -#endif - } + _engine->updateMemoryCost(deltaSize); } void ScriptManager::timerFired() { - { - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { - scriptWarningMessage("Script.timerFired() while shutting down is ignored... parent script:" + getFilename()); - return; // bail early - } + if (isStopped()) { + scriptWarningMessage("Script.timerFired() while shutting down is ignored... parent script:" + getFilename()); + return; // bail early } QTimer* callingTimer = reinterpret_cast(sender()); @@ -1388,8 +1383,7 @@ QObject* ScriptManager::setupTimerWithInterval(const ScriptValuePointer& functio } QObject* ScriptManager::setInterval(const ScriptValuePointer& function, int intervalMS) { - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { + if (isStopped()) { scriptWarningMessage("Script.setInterval() while shutting down is ignored... parent script:" + getFilename()); return NULL; // bail early } @@ -1398,8 +1392,7 @@ QObject* ScriptManager::setInterval(const ScriptValuePointer& function, int inte } QObject* ScriptManager::setTimeout(const ScriptValuePointer& function, int timeoutMS) { - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { + if (isStopped()) { scriptWarningMessage("Script.setTimeout() while shutting down is ignored... parent script:" + getFilename()); return NULL; // bail early } @@ -1431,10 +1424,12 @@ QUrl ScriptManager::resolvePath(const QString& include) const { // to the first absolute URL in the JS scope chain QUrl parentURL; auto context = _engine->currentContext(); + ScriptContextPointer parentContext; // using this variable to maintain parent variable lifespan do { auto contextInfo = context->functionContext(); parentURL = QUrl(contextInfo->fileName()); - context = context->parentContext(); + parentContext = context->parentContext(); + context = parentContext.data(); } while (parentURL.isRelative() && context); if (parentURL.isRelative()) { @@ -1574,8 +1569,9 @@ ScriptValuePointer ScriptManager::currentModule() { auto jsRequire = _engine->globalObject()->property("Script")->property("require"); auto cache = jsRequire->property("cache"); auto candidate = ScriptValuePointer(); - for (auto c = _engine->currentContext(); c && !candidate->isObject(); c = c->parentContext()) { - auto contextInfo = c->functionContext(); + ScriptContextPointer parentContext; // using this variable to maintain parent variable lifespan + for (auto context = _engine->currentContext(); context && !candidate->isObject(); parentContext = context->parentContext(), context = parentContext.data()) { + auto contextInfo = context->functionContext(); candidate = cache->property(contextInfo->fileName()); } if (!candidate->isObject()) { @@ -1833,8 +1829,7 @@ void ScriptManager::include(const QStringList& includeFiles, ScriptValuePointer if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { return; } - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { + if (isStopped()) { scriptWarningMessage("Script.include() while shutting down is ignored... includeFiles:" + includeFiles.join(",") + "parent script:" + getFilename()); return; // bail early @@ -1928,8 +1923,7 @@ void ScriptManager::include(const QStringList& includeFiles, ScriptValuePointer } void ScriptManager::include(const QString& includeFile, ScriptValuePointer callback) { - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { + if (isStopped()) { scriptWarningMessage("Script.include() while shutting down is ignored... includeFile:" + includeFile + "parent script:" + getFilename()); return; // bail early @@ -1947,8 +1941,7 @@ void ScriptManager::load(const QString& loadFile) { if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { return; } - QSharedPointer scriptEngines(_scriptEngines); - if (!scriptEngines || scriptEngines->isStopped()) { + if (isStopped()) { scriptWarningMessage("Script.load() while shutting down is ignored... loadFile:" + loadFile + "parent script:" + getFilename()); return; // bail early diff --git a/libraries/script-engine/src/ScriptManager.h b/libraries/script-engine/src/ScriptManager.h index 3fd77800d3f..b7d01bcd267 100644 --- a/libraries/script-engine/src/ScriptManager.h +++ b/libraries/script-engine/src/ScriptManager.h @@ -177,10 +177,12 @@ class ScriptManager : public QObject, public EntitiesScriptEngineProvider, publi QString getFilename() const; - ScriptEnginePointer engine(); + inline ScriptEnginePointer engine() { return _engine; } QList getListOfEntityScriptIDs(); + bool isStopped() const; + /*@jsdoc * Stops and unloads the current script. *

Warning: If an assignment client script, the script gets restarted after stopping.

diff --git a/libraries/script-engine/src/ScriptValue.h b/libraries/script-engine/src/ScriptValue.h index b3c90e8e444..67a8f8eb607 100644 --- a/libraries/script-engine/src/ScriptValue.h +++ b/libraries/script-engine/src/ScriptValue.h @@ -52,6 +52,7 @@ class ScriptValue { virtual ScriptValuePointer call(const ScriptValuePointer& thisObject, const ScriptValuePointer& arguments) = 0; virtual ScriptValuePointer construct(const ScriptValueList& args = ScriptValueList()) = 0; virtual ScriptValuePointer construct(const ScriptValuePointer& arguments) = 0; + virtual ScriptValuePointer data() const = 0; virtual ScriptEnginePointer engine() const = 0; inline bool equals(const ScriptValuePointer& other) const; inline bool isArray() const; @@ -68,6 +69,7 @@ class ScriptValue { virtual ScriptValueIteratorPointer newIterator() = 0; virtual ScriptValuePointer property(const QString& name, const ResolveFlags& mode = ResolvePrototype) const = 0; virtual ScriptValuePointer property(quint32 arrayIndex, const ResolveFlags& mode = ResolvePrototype) const = 0; + virtual void setData(const ScriptValuePointer& val) = 0; virtual void setProperty(const QString& name, const ScriptValuePointer& value, const PropertyFlags& flags = KeepExistingFlags) = 0; diff --git a/libraries/script-engine/src/Scriptable.cpp b/libraries/script-engine/src/Scriptable.cpp new file mode 100644 index 00000000000..b205e6c2880 --- /dev/null +++ b/libraries/script-engine/src/Scriptable.cpp @@ -0,0 +1,24 @@ +// +// Scriptable.cpp +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/22/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Scriptable.h" + +#include + +static QThreadStorage ScriptContextStore; + +ScriptContext* Scriptable::context() { + return ScriptContextStore.localData(); +} + +void Scriptable::setContext(ScriptContext* context) { + ScriptContextStore.setLocalData(context); +} diff --git a/libraries/script-engine/src/qtscript/ArrayBufferClass.cpp b/libraries/script-engine/src/qtscript/ArrayBufferClass.cpp new file mode 100644 index 00000000000..3902096e52f --- /dev/null +++ b/libraries/script-engine/src/qtscript/ArrayBufferClass.cpp @@ -0,0 +1,180 @@ +// +// ArrayBufferClass.cpp +// +// +// Created by Clement on 7/3/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ArrayBufferClass.h" + +#include + +#include "ArrayBufferPrototype.h" +#include "DataViewClass.h" +#include "ScriptEngineQtScript.h" +#include "TypedArrays.h" + + +static const QString CLASS_NAME = "ArrayBuffer"; + +// FIXME: Q_DECLARE_METATYPE is global and really belongs in a shared header file, not per .cpp like this +// (see DataViewClass.cpp, etc. which would also have to be updated to resolve) +Q_DECLARE_METATYPE(QScriptClass*) +Q_DECLARE_METATYPE(QByteArray*) +int qScriptClassPointerMetaTypeId = qRegisterMetaType(); +int qByteArrayPointerMetaTypeId = qRegisterMetaType(); + +ArrayBufferClass::ArrayBufferClass(ScriptEngineQtScript* scriptEngine) : +QObject(scriptEngine), +QScriptClass(scriptEngine) { + qScriptRegisterMetaType(engine(), toScriptValue, fromScriptValue); + QScriptValue global = engine()->globalObject(); + + // Save string handles for quick lookup + _name = engine()->toStringHandle(CLASS_NAME.toLatin1()); + _byteLength = engine()->toStringHandle(BYTE_LENGTH_PROPERTY_NAME.toLatin1()); + + // build prototype + _proto = engine()->newQObject(new ArrayBufferPrototype(this), + QScriptEngine::QtOwnership, + QScriptEngine::SkipMethodsInEnumeration | + QScriptEngine::ExcludeSuperClassMethods | + QScriptEngine::ExcludeSuperClassProperties); + _proto.setPrototype(global.property("Object").property("prototype")); + + // Register constructor + _ctor = engine()->newFunction(construct, _proto); + _ctor.setData(engine()->toScriptValue(this)); + + engine()->globalObject().setProperty(name(), _ctor); + + // Registering other array types + // The script engine is there parent so it'll delete them with itself + new DataViewClass(scriptEngine); + new Int8ArrayClass(scriptEngine); + new Uint8ArrayClass(scriptEngine); + new Uint8ClampedArrayClass(scriptEngine); + new Int16ArrayClass(scriptEngine); + new Uint16ArrayClass(scriptEngine); + new Int32ArrayClass(scriptEngine); + new Uint32ArrayClass(scriptEngine); + new Float32ArrayClass(scriptEngine); + new Float64ArrayClass(scriptEngine); +} + +QScriptValue ArrayBufferClass::newInstance(qint32 size) { + const qint32 MAX_LENGTH = 100000000; + if (size < 0) { + engine()->evaluate("throw \"ArgumentError: negative length\""); + return QScriptValue(); + } + if (size > MAX_LENGTH) { + engine()->evaluate("throw \"ArgumentError: absurd length\""); + return QScriptValue(); + } + // We've patched qt to fix https://highfidelity.atlassian.net/browse/BUGZ-46 on mac and windows only. +#if defined(Q_OS_WIN) || defined(Q_OS_MAC) + engine()->reportAdditionalMemoryCost(size); +#endif + QScriptEngine* eng = engine(); + QVariant variant = QVariant::fromValue(QByteArray(size, 0)); + QScriptValue data = eng->newVariant(variant); + return engine()->newObject(this, data); +} + +QScriptValue ArrayBufferClass::newInstance(const QByteArray& ba) { + QScriptValue data = engine()->newVariant(QVariant::fromValue(ba)); + return engine()->newObject(this, data); +} + +QScriptValue ArrayBufferClass::construct(QScriptContext* context, QScriptEngine* engine) { + ArrayBufferClass* cls = qscriptvalue_cast(context->callee().data()); + if (!cls) { + // return if callee (function called) is not of type ArrayBuffer + return QScriptValue(); + } + QScriptValue arg = context->argument(0); + if (!arg.isValid() || !arg.isNumber()) { + return QScriptValue(); + } + + quint32 size = arg.toInt32(); + QScriptValue newObject = cls->newInstance(size); + + if (context->isCalledAsConstructor()) { + // if called with keyword new, replace this object. + context->setThisObject(newObject); + return engine->undefinedValue(); + } + + return newObject; +} + +QScriptClass::QueryFlags ArrayBufferClass::queryProperty(const QScriptValue& object, + const QScriptString& name, + QueryFlags flags, uint* id) { + QByteArray* ba = qscriptvalue_cast(object.data()); + if (ba && name == _byteLength) { + // if the property queried is byteLength, only handle read access + return flags &= HandlesReadAccess; + } + return 0; // No access +} + +QScriptValue ArrayBufferClass::property(const QScriptValue& object, + const QScriptString& name, uint id) { + QByteArray* ba = qscriptvalue_cast(object.data()); + if (ba && name == _byteLength) { + return ba->length(); + } + return QScriptValue(); +} + +QScriptValue::PropertyFlags ArrayBufferClass::propertyFlags(const QScriptValue& object, + const QScriptString& name, uint id) { + return QScriptValue::Undeletable; +} + +QString ArrayBufferClass::name() const { + return _name.toString(); +} + +QScriptValue ArrayBufferClass::prototype() const { + return _proto; +} + +QScriptValue ArrayBufferClass::toScriptValue(QScriptEngine* engine, const QByteArray& ba) { + QScriptValue ctor = engine->globalObject().property(CLASS_NAME); + ArrayBufferClass* cls = qscriptvalue_cast(ctor.data()); + if (!cls) { + if (engine->currentContext()) { + engine->currentContext()->throwError("arrayBufferClass::toScriptValue -- could not get " + CLASS_NAME + " class constructor"); + } + return QScriptValue::NullValue; + } + return cls->newInstance(ba); +} + +void ArrayBufferClass::fromScriptValue(const QScriptValue& object, QByteArray& byteArray) { + if (object.isString()) { + // UTF-8 encoded String + byteArray = object.toString().toUtf8(); + } else if (object.isArray()) { + // Array of uint8s eg: [ 128, 3, 25, 234 ] + auto Uint8Array = object.engine()->globalObject().property("Uint8Array"); + auto typedArray = Uint8Array.construct(QScriptValueList{object}); + if (QByteArray* buffer = qscriptvalue_cast(typedArray.property("buffer"))) { + byteArray = *buffer; + } + } else if (object.isObject()) { + // ArrayBuffer instance (or any JS class that supports coercion into QByteArray*) + if (QByteArray* buffer = qscriptvalue_cast(object.data())) { + byteArray = *buffer; + } + } +} + diff --git a/libraries/script-engine-qtscript/src/ArrayBufferClass.h b/libraries/script-engine/src/qtscript/ArrayBufferClass.h similarity index 100% rename from libraries/script-engine-qtscript/src/ArrayBufferClass.h rename to libraries/script-engine/src/qtscript/ArrayBufferClass.h diff --git a/libraries/script-engine-qtscript/src/ArrayBufferPrototype.cpp b/libraries/script-engine/src/qtscript/ArrayBufferPrototype.cpp similarity index 98% rename from libraries/script-engine-qtscript/src/ArrayBufferPrototype.cpp rename to libraries/script-engine/src/qtscript/ArrayBufferPrototype.cpp index 2523a47e27e..ad5a68ec796 100644 --- a/libraries/script-engine-qtscript/src/ArrayBufferPrototype.cpp +++ b/libraries/script-engine/src/qtscript/ArrayBufferPrototype.cpp @@ -15,8 +15,7 @@ #include #include - -#include "ArrayBufferClass.h" +#include static const int QCOMPRESS_HEADER_POSITION = 0; static const int QCOMPRESS_HEADER_SIZE = 4; diff --git a/libraries/script-engine-qtscript/src/ArrayBufferPrototype.h b/libraries/script-engine/src/qtscript/ArrayBufferPrototype.h similarity index 100% rename from libraries/script-engine-qtscript/src/ArrayBufferPrototype.h rename to libraries/script-engine/src/qtscript/ArrayBufferPrototype.h diff --git a/libraries/script-engine-qtscript/src/ArrayBufferViewClass.cpp b/libraries/script-engine/src/qtscript/ArrayBufferViewClass.cpp similarity index 100% rename from libraries/script-engine-qtscript/src/ArrayBufferViewClass.cpp rename to libraries/script-engine/src/qtscript/ArrayBufferViewClass.cpp diff --git a/libraries/script-engine-qtscript/src/ArrayBufferViewClass.h b/libraries/script-engine/src/qtscript/ArrayBufferViewClass.h similarity index 100% rename from libraries/script-engine-qtscript/src/ArrayBufferViewClass.h rename to libraries/script-engine/src/qtscript/ArrayBufferViewClass.h diff --git a/libraries/script-engine-qtscript/src/DataViewClass.cpp b/libraries/script-engine/src/qtscript/DataViewClass.cpp similarity index 100% rename from libraries/script-engine-qtscript/src/DataViewClass.cpp rename to libraries/script-engine/src/qtscript/DataViewClass.cpp diff --git a/libraries/script-engine-qtscript/src/DataViewClass.h b/libraries/script-engine/src/qtscript/DataViewClass.h similarity index 100% rename from libraries/script-engine-qtscript/src/DataViewClass.h rename to libraries/script-engine/src/qtscript/DataViewClass.h diff --git a/libraries/script-engine-qtscript/src/DataViewPrototype.cpp b/libraries/script-engine/src/qtscript/DataViewPrototype.cpp similarity index 98% rename from libraries/script-engine-qtscript/src/DataViewPrototype.cpp rename to libraries/script-engine/src/qtscript/DataViewPrototype.cpp index ff540f5300a..7770e3bce50 100644 --- a/libraries/script-engine-qtscript/src/DataViewPrototype.cpp +++ b/libraries/script-engine/src/qtscript/DataViewPrototype.cpp @@ -13,12 +13,14 @@ #include #include +#include +#include #include #include -#include "DataViewClass.h" +#include "ArrayBufferViewClass.h" Q_DECLARE_METATYPE(QByteArray*) diff --git a/libraries/script-engine-qtscript/src/DataViewPrototype.h b/libraries/script-engine/src/qtscript/DataViewPrototype.h similarity index 100% rename from libraries/script-engine-qtscript/src/DataViewPrototype.h rename to libraries/script-engine/src/qtscript/DataViewPrototype.h diff --git a/libraries/script-engine/src/qtscript/ScriptContextQtAgent.cpp b/libraries/script-engine/src/qtscript/ScriptContextQtAgent.cpp new file mode 100644 index 00000000000..142d2dbb2a8 --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptContextQtAgent.cpp @@ -0,0 +1,73 @@ +// +// ScriptContextQtAgent.cpp +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/22/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptContextQtAgent.h" + +#include + +#include "../Scriptable.h" +#include "ScriptContextQtWrapper.h" +#include "ScriptEngineQtScript.h" + +void ScriptContextQtAgent::contextPop() { + if (_prevAgent) { + _prevAgent->contextPop(); + } + if (_engine->currentContext() == _currContext) { + _currContext.reset(); + if (!_contextActive && !_contextStack.empty()) { + _currContext = _contextStack.back(); + _contextStack.pop_back(); + _contextActive = true; + } + } +} + +void ScriptContextQtAgent::contextPush() { + if (_prevAgent) { + _prevAgent->contextPush(); + } + if (_contextActive && _currContext) { + _contextStack.push_back(_currContext); + _contextActive = false; + } + _currContext.reset(); +} + +void ScriptContextQtAgent::functionEntry(qint64 scriptId) { + if (_prevAgent) { + _prevAgent->functionEntry(scriptId); + } + if (scriptId != -1) { + return; + } + if (!_currContext) { + _currContext = ScriptContextQtPointer(new ScriptContextQtWrapper(_engine, static_cast(_engine)->currentContext())); + } + Scriptable::setContext(_currContext.get()); + _contextActive = true; +} + +void ScriptContextQtAgent::functionExit(qint64 scriptId, const QScriptValue& returnValue) { + if (_prevAgent) { + _prevAgent->functionExit(scriptId, returnValue); + } + if (scriptId != -1) { + return; + } + _contextActive = false; + if (!_contextActive && !_contextStack.empty()) { + _currContext = _contextStack.back(); + _contextStack.pop_back(); + Scriptable::setContext(_currContext.get()); + _contextActive = true; + } +} diff --git a/libraries/script-engine/src/qtscript/ScriptContextQtAgent.h b/libraries/script-engine/src/qtscript/ScriptContextQtAgent.h new file mode 100644 index 00000000000..d9d63b20b48 --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptContextQtAgent.h @@ -0,0 +1,47 @@ +// +// ScriptContextQtAgent.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/22/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ScriptContextQtAgent_h +#define hifi_ScriptContextQtAgent_h + +#include +#include +#include + +#include "ScriptEngineQtScript.h" + +class QScriptContext; +class QScriptEngine; +class QScriptValue; +class ScriptContextQtWrapper; +using ScriptContextQtPointer = QSharedPointer; + +class ScriptContextQtAgent : public QScriptEngineAgent { +public: // construction + inline ScriptContextQtAgent(ScriptEngineQtScript* engine, QScriptEngineAgent* prevAgent) : + QScriptEngineAgent(engine), _engine(engine), _prevAgent(prevAgent) {} + virtual ~ScriptContextQtAgent() {} + +public: // QScriptEngineAgent implementation + virtual void contextPop(); + virtual void contextPush(); + virtual void functionEntry(qint64 scriptId); + virtual void functionExit(qint64 scriptId, const QScriptValue& returnValue); + +private: // storage + bool _contextActive = false; + QList _contextStack; + ScriptContextQtPointer _currContext = nullptr; + ScriptEngineQtScript* _engine; + QScriptEngineAgent* _prevAgent; +}; + +#endif // hifi_ScriptContextQtAgent_h \ No newline at end of file diff --git a/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.cpp b/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.cpp new file mode 100644 index 00000000000..31cc3a51d1b --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.cpp @@ -0,0 +1,73 @@ +// +// ScriptContextQtWrapper.cpp +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/22/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptContextQtWrapper.h" + +#include + +#include "ScriptEngineQtScript.h" +#include "ScriptValueQtWrapper.h" + +ScriptContextQtWrapper* ScriptContextQtWrapper::unwrap(ScriptContext* val) { + if (!val) { + return nullptr; + } + + return dynamic_cast(val); +} + +int ScriptContextQtWrapper::argumentCount() const { + return _context->argumentCount(); +} + +ScriptValuePointer ScriptContextQtWrapper::argument(int index) const { + QScriptValue result = _context->argument(index); + return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +QStringList ScriptContextQtWrapper::backtrace() const { + return _context->backtrace(); +} + +ScriptValuePointer ScriptContextQtWrapper::callee() const { + QScriptValue result = _context->callee(); + return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +ScriptEnginePointer ScriptContextQtWrapper::engine() const { + return _engine->sharedFromThis(); +} + +//ScriptFunctionContext* ScriptContextQtWrapper::functionContext() const; + +ScriptContextPointer ScriptContextQtWrapper::parentContext() const { + QScriptContext* result = _context->parentContext(); + return ScriptContextPointer(new ScriptContextQtWrapper(_engine, result)); +} + +ScriptValuePointer ScriptContextQtWrapper::thisObject() const { + QScriptValue result = _context->thisObject(); + return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +ScriptValuePointer ScriptContextQtWrapper::throwError(const QString& text) { + QScriptValue result = _context->throwError(text); + return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +ScriptValuePointer ScriptContextQtWrapper::throwValue(const ScriptValuePointer& value) { + ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(value); + if (!unwrapped) { + return _engine->undefinedValue(); + } + QScriptValue result = _context->throwValue(unwrapped->toQtValue()); + return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result))); +} diff --git a/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.h b/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.h new file mode 100644 index 00000000000..86decb768ab --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.h @@ -0,0 +1,48 @@ +// +// ScriptContextQtWrapper.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/22/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ScriptContextQtWrapper_h +#define hifi_ScriptContextQtWrapper_h + +#include +#include + +#include "../ScriptContext.h" + +class QScriptContext; +class ScriptEngineQtScript; +class ScriptValue; +using ScriptValuePointer = QSharedPointer; + +class ScriptContextQtWrapper : public ScriptContext { +public: // construction + inline ScriptContextQtWrapper(ScriptEngineQtScript* engine, QScriptContext* context) : _engine(engine), _context(context) {} + static ScriptContextQtWrapper* unwrap(ScriptContext* val); + inline QScriptContext* toQtValue() const { return _context; } + +public: // ScriptContext implementation + virtual int argumentCount() const; + virtual ScriptValuePointer argument(int index) const; + virtual QStringList backtrace() const; + virtual ScriptValuePointer callee() const; + virtual ScriptEnginePointer engine() const; + virtual ScriptFunctionContextPointer functionContext() const; + virtual ScriptContextPointer parentContext() const; + virtual ScriptValuePointer thisObject() const; + virtual ScriptValuePointer throwError(const QString& text); + virtual ScriptValuePointer throwValue(const ScriptValuePointer& value); + +private: // storage + QScriptContext* _context; + ScriptEngineQtScript* _engine; +}; + +#endif // hifi_ScriptContextQtWrapper_h \ No newline at end of file diff --git a/libraries/script-engine/src/qtscript/ScriptEngineQtScript.cpp b/libraries/script-engine/src/qtscript/ScriptEngineQtScript.cpp new file mode 100644 index 00000000000..201c40b6ddd --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptEngineQtScript.cpp @@ -0,0 +1,837 @@ +// +// ScriptEngineQtScript.cpp +// libraries/script-engine-qtscript/src +// +// Created by Brad Hefta-Gaub on 12/14/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptEngineQtScript.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include "../ScriptEngineLogging.h" +#include "../ScriptProgram.h" +#include "../ScriptValue.h" + +#include "ScriptProgramQtWrapper.h" +#include "ScriptValueQtWrapper.h" + +static const int MAX_MODULE_ID_LENGTH { 4096 }; +static const int MAX_DEBUG_VALUE_LENGTH { 80 }; + +static const QScriptEngine::QObjectWrapOptions DEFAULT_QOBJECT_WRAP_OPTIONS = + QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects; +static const QScriptValue::PropertyFlags READONLY_PROP_FLAGS { QScriptValue::ReadOnly | QScriptValue::Undeletable }; +static const QScriptValue::PropertyFlags READONLY_HIDDEN_PROP_FLAGS { READONLY_PROP_FLAGS | QScriptValue::SkipInEnumeration }; + +static const bool HIFI_AUTOREFRESH_FILE_SCRIPTS { true }; + +Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature) +int qfunctionSignatureMetaID = qRegisterMetaType(); + +int scriptEnginePointerMetaID = qRegisterMetaType(); + +bool ScriptEngineQtScript::IS_THREADSAFE_INVOCATION(const QThread* thread, const QString& method) { + if (QThread::currentThread() == thread) { + return true; + } + qCCritical(scriptengine) << QString("Scripting::%1 @ %2 -- ignoring thread-unsafe call from %3") + .arg(method) + .arg(thread ? thread->objectName() : "(!thread)") + .arg(QThread::currentThread()->objectName()); + qCDebug(scriptengine) << "(please resolve on the calling side by using invokeMethod, executeOnScriptThread, etc.)"; + Q_ASSERT(false); + return false; +} + +// engine-aware JS Error copier and factory +QScriptValue ScriptEngineQtScript::makeError(const QScriptValue& _other, const QString& type) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + auto other = _other; + if (other.isString()) { + other = QScriptEngine::newObject(); + other.setProperty("message", _other.toString()); + } + auto proto = QScriptEngine::globalObject().property(type); + if (!proto.isFunction()) { + proto = QScriptEngine::globalObject().property(other.prototype().property("constructor").property("name").toString()); + } + if (!proto.isFunction()) { +#ifdef DEBUG_JS_EXCEPTIONS + qCDebug(shared) << "BaseScriptEngine::makeError -- couldn't find constructor for" << type << " -- using Error instead"; +#endif + proto = QScriptEngine::globalObject().property("Error"); + } + if (other.engine() != this) { + // JS Objects are parented to a specific script engine instance + // -- this effectively ~clones it locally by routing through a QVariant and back + other = QScriptEngine::toScriptValue(other.toVariant()); + } + // ~ var err = new Error(other.message) + auto err = proto.construct(QScriptValueList({ other.property("message") })); + + // transfer over any existing properties + QScriptValueIterator it(other); + while (it.hasNext()) { + it.next(); + err.setProperty(it.name(), it.value()); + } + return err; +} + +ScriptValuePointer ScriptEngineQtScript::makeError(const ScriptValuePointer& _other, const QString& type) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(_other); + QScriptValue other; + if (_other->isString()) { + other = QScriptEngine::newObject(); + other.setProperty("message", _other->toString()); + } else if (unwrapped) { + other = unwrapped->toQtValue(); + } else { + other = QScriptEngine::newVariant(_other->toVariant()); + } + QScriptValue result = makeError(other, type); + return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result))); +} + +// check syntax and when there are issues returns an actual "SyntaxError" with the details +ScriptValuePointer ScriptEngineQtScript::lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + const auto syntaxCheck = checkSyntax(sourceCode); + if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { + auto err = QScriptEngine::globalObject().property("SyntaxError").construct(QScriptValueList({ syntaxCheck.errorMessage() })); + err.setProperty("fileName", fileName); + err.setProperty("lineNumber", syntaxCheck.errorLineNumber()); + err.setProperty("expressionBeginOffset", syntaxCheck.errorColumnNumber()); + err.setProperty("stack", currentContext()->backtrace().join(ScriptManager::SCRIPT_BACKTRACE_SEP)); + { + const auto error = syntaxCheck.errorMessage(); + const auto line = QString::number(syntaxCheck.errorLineNumber()); + const auto column = QString::number(syntaxCheck.errorColumnNumber()); + // for compatibility with legacy reporting + const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, fileName, line, column); + err.setProperty("formatted", message); + } + return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(err))); + } + return undefinedValue(); +} + +// this pulls from the best available information to create a detailed snapshot of the current exception +ScriptValuePointer ScriptEngineQtScript::cloneUncaughtException(const QString& extraDetail) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + if (!hasUncaughtException()) { + return nullValue(); + } + auto exception = uncaughtException(); + // ensure the error object is engine-local + auto err = makeError(exception); + + // not sure why Qt does't offer uncaughtExceptionFileName -- but the line number + // on its own is often useless/wrong if arbitrarily married to a filename. + // when the error object already has this info, it seems to be the most reliable + auto fileName = exception->property("fileName")->toString(); + auto lineNumber = exception->property("lineNumber")->toInt32(); + + // the backtrace, on the other hand, seems most reliable taken from uncaughtExceptionBacktrace + auto backtrace = uncaughtExceptionBacktrace(); + if (backtrace.isEmpty()) { + // fallback to the error object + backtrace = exception->property("stack")->toString().split(ScriptManager::SCRIPT_BACKTRACE_SEP); + } + // the ad hoc "detail" property can be used now to embed additional clues + auto detail = exception->property("detail")->toString(); + if (detail.isEmpty()) { + detail = extraDetail; + } else if (!extraDetail.isEmpty()) { + detail += "(" + extraDetail + ")"; + } + if (lineNumber <= 0) { + lineNumber = uncaughtExceptionLineNumber(); + } + if (fileName.isEmpty()) { + // climb the stack frames looking for something useful to display + for (auto c = QScriptEngine::currentContext(); c && fileName.isEmpty(); c = c->parentContext()) { + QScriptContextInfo info{ c }; + if (!info.fileName().isEmpty()) { + // take fileName:lineNumber as a pair + fileName = info.fileName(); + lineNumber = info.lineNumber(); + if (backtrace.isEmpty()) { + backtrace = c->backtrace(); + } + break; + } + } + } + err->setProperty("fileName", fileName); + err->setProperty("lineNumber", lineNumber); + err->setProperty("detail", detail); + err->setProperty("stack", backtrace.join(ScriptManager::SCRIPT_BACKTRACE_SEP)); + +#ifdef DEBUG_JS_EXCEPTIONS + err->setProperty("_fileName", exception.property("fileName").toString()); + err->setProperty("_stack", uncaughtExceptionBacktrace().join(SCRIPT_BACKTRACE_SEP)); + err->setProperty("_lineNumber", uncaughtExceptionLineNumber()); +#endif + return err; +} + +bool ScriptEngineQtScript::raiseException(const QScriptValue& exception) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return false; + } + if (QScriptEngine::currentContext()) { + // we have an active context / JS stack frame so throw the exception per usual + QScriptEngine::currentContext()->throwValue(makeError(exception)); + return true; + } else if(_manager) { + // we are within a pure C++ stack frame (ie: being called directly by other C++ code) + // in this case no context information is available so just emit the exception for reporting + QScriptValue thrown = makeError(exception); + emit _manager->unhandledException(ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(thrown)))); + } + return false; +} + +bool ScriptEngineQtScript::maybeEmitUncaughtException(const QString& debugHint) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return false; + } + if (!isEvaluating() && hasUncaughtException() && _manager) { + emit _manager->unhandledException(cloneUncaughtException(debugHint)); + clearExceptions(); + return true; + } + return false; +} + +// Lambda +QScriptValue ScriptEngineQtScript::newLambdaFunction(std::function operation, + const QScriptValue& data, + const QScriptEngine::ValueOwnership& ownership) { + auto lambda = new Lambda(this, operation, data); + auto object = QScriptEngine::newQObject(lambda, ownership); + auto call = object.property("call"); + call.setPrototype(object); // context->callee().prototype() === Lambda QObject + call.setData(data); // context->callee().data() will === data param + return call; +} +QString Lambda::toString() const { + return QString("[Lambda%1]").arg(data.isValid() ? " " + data.toString() : data.toString()); +} + +Lambda::~Lambda() { +#ifdef DEBUG_JS_LAMBDA_FUNCS + qDebug() << "~Lambda" + << "this" << this; +#endif +} + +Lambda::Lambda(ScriptEngineQtScript* engine, + std::function operation, + QScriptValue data) : + engine(engine), + operation(operation), data(data) { +#ifdef DEBUG_JS_LAMBDA_FUNCS + qDebug() << "Lambda" << data.toString(); +#endif +} +QScriptValue Lambda::call() { + if (!engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { + return engine->nullValue(); + } + return operation(static_cast(engine)->currentContext(), engine); +} + +QScriptValue makeScopedHandlerObject(QScriptValue scopeOrCallback, QScriptValue methodOrName) { + auto engine = scopeOrCallback.engine(); + if (!engine) { + return scopeOrCallback; + } + auto scope = QScriptValue(); + auto callback = scopeOrCallback; + if (scopeOrCallback.isObject()) { + if (methodOrName.isString()) { + scope = scopeOrCallback; + callback = scope.property(methodOrName.toString()); + } else if (methodOrName.isFunction()) { + scope = scopeOrCallback; + callback = methodOrName; + } else if (!methodOrName.isValid()) { + // instantiate from an existing scoped handler object + if (scopeOrCallback.property("callback").isFunction()) { + scope = scopeOrCallback.property("scope"); + callback = scopeOrCallback.property("callback"); + } + } + } + auto handler = engine->newObject(); + handler.setProperty("scope", scope); + handler.setProperty("callback", callback); + return handler; +} + +QScriptValue callScopedHandlerObject(QScriptValue handler, QScriptValue err, QScriptValue result) { + return handler.property("callback").call(handler.property("scope"), QScriptValueList({ err, result })); +} + +#ifdef DEBUG_JS +void ScriptEngineQtScript::_debugDump(const QString& header, const QScriptValue& object, const QString& footer) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return; + } + if (!header.isEmpty()) { + qCDebug(shared) << header; + } + if (!object.isObject()) { + qCDebug(shared) << "(!isObject)" << object.toVariant().toString() << object.toString(); + return; + } + QScriptValueIterator it(object); + while (it.hasNext()) { + it.next(); + qCDebug(shared) << it.name() << ":" << it.value().toString(); + } + if (!footer.isEmpty()) { + qCDebug(shared) << footer; + } +} +#endif + +static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) { + // assemble the message by concatenating our arguments + QString message = ""; + for (int i = 0; i < context->argumentCount(); i++) { + if (i > 0) { + message += " "; + } + message += context->argument(i).toString(); + } + + // was this generated by a script engine? If we don't recognize it then send the message and exit + ScriptEngineQtScript* scriptEngine = qobject_cast(engine); + if (!scriptEngine) { + qCDebug(scriptengine_script, "%s", qUtf8Printable(message)); + return QScriptValue(); + } + + QString filename; + auto scriptManager = scriptEngine->manager(); + if (scriptManager) { + filename = scriptManager->getFilename(); + } + + // This message was sent by one of our script engines, let's try to see if we can find the source. + // Note that the first entry in the backtrace should be "print" and is somewhat useless to us + AbstractLoggerInterface* loggerInterface = AbstractLoggerInterface::get(); + if (loggerInterface && loggerInterface->showSourceDebugging()) { + QScriptContext* userContext = context; + while (userContext && QScriptContextInfo(userContext).functionType() == QScriptContextInfo::NativeFunction) { + userContext = userContext->parentContext(); + } + QString location; + if (userContext) { + QScriptContextInfo contextInfo(userContext); + QString fileName = contextInfo.fileName(); + int lineNumber = contextInfo.lineNumber(); + QString functionName = contextInfo.functionName(); + + location = functionName; + if (!fileName.isEmpty()) { + if (location.isEmpty()) { + location = fileName; + } else { + location = QString("%1 at %2").arg(location).arg(fileName); + } + } + if (lineNumber != -1) { + location = QString("%1:%2").arg(location).arg(lineNumber); + } + } + if (location.isEmpty()) { + location = filename; + } + + // give the script engine a chance to notify the system about this message + scriptEngine->print(message); + + // send the message to debug log + qCDebug(scriptengine_script, "[%s] %s", qUtf8Printable(location), qUtf8Printable(message)); + } else { + scriptEngine->print(message); + // prefix the script engine name to help disambiguate messages in the main debug log + qCDebug(scriptengine_script, "[%s] %s", qUtf8Printable(filename), qUtf8Printable(message)); + } + + return QScriptValue(); +} + +ScriptEngineQtScript::ScriptEngineQtScript(ScriptManager* scriptManager) : + QScriptEngine(), + _manager(scriptManager), + _arrayBufferClass(new ArrayBufferClass(this)) +{ + if (_manager) { + connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) { + if (hasUncaughtException()) { + // the engine's uncaughtException() seems to produce much better stack traces here + emit _manager->unhandledException(cloneUncaughtException("signalHandlerException")); + clearExceptions(); + } else { + // ... but may not always be available -- so if needed we fallback to the passed exception + QScriptValue thrown = makeError(exception); + emit _manager->unhandledException(ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(thrown)))); + } + }, Qt::DirectConnection); + } + + QScriptValue null = QScriptEngine::nullValue(); + _nullValue = ScriptValuePointer(new ScriptValueQtWrapper(const_cast(this), std::move(null))); + + QScriptValue undefined = QScriptEngine::undefinedValue(); + _undefinedValue = ScriptValuePointer(new ScriptValueQtWrapper(const_cast(this), std::move(undefined))); + + setProcessEventsInterval(MSECS_PER_SECOND); +} + +bool ScriptEngineQtScript::isDebugMode() const { +#if defined(DEBUG) + return true; +#else + return false; +#endif +} + +ScriptEngineQtScript::~ScriptEngineQtScript() {} + +void ScriptEngineQtScript::disconnectNonEssentialSignals() { + disconnect(); + QThread* workerThread; + // Ensure the thread should be running, and does exist + if (_isRunning && _isThreaded && (workerThread = QScriptEngine::thread())) { + connect(this, &QObject::destroyed, workerThread, &QThread::quit); + connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); + } +} + +void ScriptEngineQtScript::executeOnScriptThread(std::function function, const Qt::ConnectionType& type ) { + if (QThread::currentThread() != QScriptEngine::thread()) { + QMetaObject::invokeMethod(this, "executeOnScriptThread", type, Q_ARG(std::function, function)); + return; + } + + function(); +} + +void ScriptEngineQtScript::registerValue(const QString& valueName, QScriptValue value) { + if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerValue() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; +#endif + QMetaObject::invokeMethod(this, "registerValue", + Q_ARG(const QString&, valueName), + Q_ARG(QScriptValue, value)); + return; + } + + QStringList pathToValue = valueName.split("."); + int partsToGo = pathToValue.length(); + QScriptValue partObject = globalObject(); + + for (const auto& pathPart : pathToValue) { + partsToGo--; + if (!partObject.property(pathPart).isValid()) { + if (partsToGo > 0) { + //QObject *object = new QObject; + QScriptValue partValue = newArray(); //newQObject(object, QScriptEngine::ScriptOwnership); + partObject.setProperty(pathPart, partValue); + } else { + partObject.setProperty(pathPart, value); + } + } + partObject = partObject.property(pathPart); + } +} + +void ScriptEngineQtScript::registerGlobalObject(const QString& name, QObject* object) { + if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerGlobalObject() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name; +#endif + QMetaObject::invokeMethod(this, "registerGlobalObject", + Q_ARG(const QString&, name), + Q_ARG(QObject*, object)); + return; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptEngineQtScript::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name; +#endif + + if (!QScriptEngine::globalObject().property(name).isValid()) { + if (object) { + QScriptValue value = QScriptEngine::newQObject(object, QScriptEngine::QtOwnership, DEFAULT_QOBJECT_WRAP_OPTIONS); + QScriptEngine::globalObject().setProperty(name, value); + } else { + QScriptEngine::globalObject().setProperty(name, QScriptValue()); + } + } +} + +void ScriptEngineQtScript::registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { + if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name; +#endif + QMetaObject::invokeMethod(this, "registerFunction", + Q_ARG(const QString&, name), + Q_ARG(QScriptEngine::FunctionSignature, functionSignature), + Q_ARG(int, numArguments)); + return; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name; +#endif + + QScriptValue scriptFun = QScriptEngine::newFunction(functionSignature, numArguments); + QScriptEngine::globalObject().setProperty(name, scriptFun); +} + +void ScriptEngineQtScript::registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) { + if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] parent:" << parent << "name:" << name; +#endif + QMetaObject::invokeMethod(this, "registerFunction", + Q_ARG(const QString&, name), + Q_ARG(QScriptEngine::FunctionSignature, functionSignature), + Q_ARG(int, numArguments)); + return; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name; +#endif + + QScriptValue object = QScriptEngine::globalObject().property(parent); + if (object.isValid()) { + QScriptValue scriptFun = QScriptEngine::newFunction(functionSignature, numArguments); + object.setProperty(name, scriptFun); + } +} + +void ScriptEngineQtScript::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, + QScriptEngine::FunctionSignature setter, const QString& parent) { + if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::registerGetterSetter() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + " name:" << name << "parent:" << parent; +#endif + QMetaObject::invokeMethod(this, "registerGetterSetter", + Q_ARG(const QString&, name), + Q_ARG(QScriptEngine::FunctionSignature, getter), + Q_ARG(QScriptEngine::FunctionSignature, setter), + Q_ARG(const QString&, parent)); + return; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptEngineQtScript::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent; +#endif + + QScriptValue setterFunction = QScriptEngine::newFunction(setter, 1); + QScriptValue getterFunction = QScriptEngine::newFunction(getter); + + if (!parent.isNull() && !parent.isEmpty()) { + QScriptValue object = QScriptEngine::globalObject().property(parent); + if (object.isValid()) { + object.setProperty(name, setterFunction, QScriptValue::PropertySetter); + object.setProperty(name, getterFunction, QScriptValue::PropertyGetter); + } + } else { + QScriptEngine::globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter); + QScriptEngine::globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter); + } +} + +ScriptValuePointer ScriptEngineQtScript::evaluateInClosure(const ScriptValuePointer& _closure, + const ScriptProgramPointer& _program) { + PROFILE_RANGE(script, "evaluateInClosure"); + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + ScriptProgramQtWrapper* unwrappedProgram = ScriptProgramQtWrapper::unwrap(_program); + if (unwrappedProgram == nullptr) { + return nullValue(); + } + const QScriptProgram& program = unwrappedProgram->toQtValue(); + + const auto fileName = program.fileName(); + const auto shortName = QUrl(fileName).fileName(); + + ScriptValueQtWrapper* unwrappedClosure = ScriptValueQtWrapper::unwrap(_closure); + if (unwrappedClosure == nullptr) { + return nullValue(); + } + const QScriptValue& closure = unwrappedClosure->toQtValue(); + + QScriptValue oldGlobal; + auto global = closure.property("global"); + if (global.isObject()) { +#ifdef DEBUG_JS + qCDebug(shared) << " setting global = closure.global" << shortName; +#endif + oldGlobal = QScriptEngine::globalObject(); + setGlobalObject(global); + } + + auto context = pushContext(); + + auto thiz = closure.property("this"); + if (thiz.isObject()) { +#ifdef DEBUG_JS + qCDebug(shared) << " setting this = closure.this" << shortName; +#endif + context->setThisObject(thiz); + } + + context->pushScope(closure); +#ifdef DEBUG_JS + qCDebug(shared) << QString("[%1] evaluateInClosure %2").arg(isEvaluating()).arg(shortName); +#endif + ScriptValuePointer result; + { + auto qResult = QScriptEngine::evaluate(program); + + if (hasUncaughtException()) { + auto err = cloneUncaughtException(__FUNCTION__); +#ifdef DEBUG_JS_EXCEPTIONS + qCWarning(shared) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString(); + err.setProperty("_result", result); +#endif + result = err; + } else { + result = ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(qResult))); + } + } +#ifdef DEBUG_JS + qCDebug(shared) << QString("[%1] //evaluateInClosure %2").arg(isEvaluating()).arg(shortName); +#endif + popContext(); + + if (oldGlobal.isValid()) { +#ifdef DEBUG_JS + qCDebug(shared) << " restoring global" << shortName; +#endif + setGlobalObject(oldGlobal); + } + + return result; +} + +ScriptValuePointer ScriptEngineQtScript::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) { + if (_manager && _manager->isStopped()) { + return undefinedValue(); // bail early + } + + if (QThread::currentThread() != QScriptEngine::thread()) { + ScriptValuePointer result; +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "sourceCode:" << sourceCode << " fileName:" << fileName << "lineNumber:" << lineNumber; +#endif + BLOCKING_INVOKE_METHOD(this, "evaluate", + Q_RETURN_ARG(ScriptValuePointer, result), + Q_ARG(const QString&, sourceCode), + Q_ARG(const QString&, fileName), + Q_ARG(int, lineNumber)); + return result; + } + + // Check syntax + auto syntaxError = lintScript(sourceCode, fileName); + if (syntaxError->isError()) { + if (!isEvaluating()) { + syntaxError->setProperty("detail", "evaluate"); + } + raiseException(syntaxError); + maybeEmitUncaughtException("lint"); + return syntaxError; + } + QScriptProgram program { sourceCode, fileName, lineNumber }; + if (program.isNull()) { + // can this happen? + auto err = makeError(newValue("could not create QScriptProgram for " + fileName)); + raiseException(err); + maybeEmitUncaughtException("compile"); + return err; + } + + QScriptValue result; + { + result = QScriptEngine::evaluate(program); + maybeEmitUncaughtException("evaluate"); + } + return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result))); +} + +void ScriptEngineQtScript::updateMemoryCost(const qint64& deltaSize) { + if (deltaSize > 0) { + // We've patched qt to fix https://highfidelity.atlassian.net/browse/BUGZ-46 on mac and windows only. +#if defined(Q_OS_WIN) || defined(Q_OS_MAC) + reportAdditionalMemoryCost(deltaSize); +#endif + } +} + +void ScriptEngineQtScript::print(const QString& message) { + QString filename; + auto scriptManager = manager(); + if (scriptManager) { + filename = scriptManager->getFilename(); + } + + emit printedMessage(message, filename); +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ScriptEngine implementation + +ScriptValuePointer ScriptEngineQtScript::globalObject() const { + QScriptValue global = QScriptEngine::globalObject(); // can't cache the value as it may change + return ScriptValuePointer(new ScriptValueQtWrapper(const_cast < ScriptEngineQtScript*>(this), std::move(global))); +} + +ScriptManager* ScriptEngineQtScript::manager() const { + return _manager; +} + +ScriptValuePointer ScriptEngineQtScript::newArray(uint length) { + QScriptValue result = QScriptEngine::newArray(length); + return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValuePointer ScriptEngineQtScript::newArrayBuffer(const QByteArray& message) { + QScriptValue data = QScriptEngine::newVariant(QVariant::fromValue(message)); + QScriptValue ctor = QScriptEngine::globalObject().property("ArrayBuffer"); + auto array = qscriptvalue_cast(ctor.data()); + if (!array) { + return undefinedValue(); + } + QScriptValue result = QScriptEngine::newObject(array, data); + return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValuePointer ScriptEngineQtScript::newObject() { + QScriptValue result = QScriptEngine::newObject(); + return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result))); +} +/* +ScriptProgramPointer ScriptEngineQtScript::newProgram(const QString& sourceCode, const QString& fileName) { +} +*/ +ScriptValuePointer ScriptEngineQtScript::newQObject(QObject* obj, + ScriptEngine::ValueOwnership ownership, + const ScriptEngine::QObjectWrapOptions& options) { + QScriptValue result = QScriptEngine::newQObject(obj, static_cast(ownership), + (QScriptEngine::QObjectWrapOptions)(int)options); + return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValuePointer ScriptEngineQtScript::newValue(bool value) { + QScriptValue result(this, value); + return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValuePointer ScriptEngineQtScript::newValue(int value) { + QScriptValue result(this, value); + return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValuePointer ScriptEngineQtScript::newValue(uint value) { + QScriptValue result(this, value); + return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValuePointer ScriptEngineQtScript::newValue(double value) { + QScriptValue result(this, value); + return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValuePointer ScriptEngineQtScript::newValue(const QString& value) { + QScriptValue result(this, value); + return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValuePointer ScriptEngineQtScript::newValue(const QLatin1String& value) { + QScriptValue result(this, value); + return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValuePointer ScriptEngineQtScript::newValue(const char* value) { + QScriptValue result(this, value); + return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValuePointer ScriptEngineQtScript::newVariant(const QVariant& value) { + QScriptValue result = QScriptEngine::newVariant(value); + return ScriptValuePointer(new ScriptValueQtWrapper(this, std::move(result))); +} + +ScriptValuePointer ScriptEngineQtScript::nullValue() { + return _nullValue; +} + +void ScriptEngineQtScript::setDefaultPrototype(int metaTypeId, const ScriptValuePointer& prototype){ + ScriptValueQtWrapper* unwrappedPrototype = ScriptValueQtWrapper::unwrap(prototype); + if (unwrappedPrototype) { + QScriptEngine::setDefaultPrototype(metaTypeId, unwrappedPrototype->toQtValue()); + } +} + +ScriptValuePointer ScriptEngineQtScript::undefinedValue() { + return _undefinedValue; +} diff --git a/libraries/script-engine-qtscript/src/ScriptEngineQtScript.h b/libraries/script-engine/src/qtscript/ScriptEngineQtScript.h similarity index 69% rename from libraries/script-engine-qtscript/src/ScriptEngineQtScript.h rename to libraries/script-engine/src/qtscript/ScriptEngineQtScript.h index f42da037792..98a8649aa8c 100644 --- a/libraries/script-engine-qtscript/src/ScriptEngineQtScript.h +++ b/libraries/script-engine/src/qtscript/ScriptEngineQtScript.h @@ -13,50 +13,25 @@ #ifndef hifi_ScriptEngineQtScript_h #define hifi_ScriptEngineQtScript_h -//#include -//#include - -//#include -//#include -//#include -//#include -//#include - #include +#include #include #include #include +#include +#include + #include #include -#include -//#include -//#include -//#include -//#include -//#include -//#include -//#include -#include -#include - -//#include "PointerEvent.h" +#include "../ScriptEngine.h" +#include "../ScriptManager.h" + #include "ArrayBufferClass.h" -//#include "AssetScriptingInterface.h" -//#include "AudioScriptingInterface.h" -#include "BaseScriptEngine.h" -//#include "ExternalResource.h" -//#include "Quat.h" -//#include "Mat4.h" -//#include "ScriptCache.h" -//#include "ScriptUUID.h" -//#include "Vec3.h" -//#include "ConsoleScriptingInterface.h" -//#include "SettingHandle.h" -//#include "Profile.h" - -//class QScriptEngineDebugger; + class ScriptManager; +class ScriptEngineQtScript; +using ScriptEngineQtScriptPointer = QSharedPointer; Q_DECLARE_METATYPE(ScriptEngineQtScriptPointer) @@ -92,18 +67,34 @@ Q_DECLARE_METATYPE(ScriptEngineQtScriptPointer) * Read-only. * @property {Script.ResourceBuckets} ExternalPaths - External resource buckets. */ -class ScriptEngineQtScript : public BaseScriptEngine, public ScriptEngine { +class ScriptEngineQtScript : public QScriptEngine, public ScriptEngine, public QEnableSharedFromThis { Q_OBJECT - using Base = BaseScriptEngine; public: // ScriptEngine implementation + virtual void abortEvaluation(); + virtual void clearExceptions(); + virtual ScriptValuePointer cloneUncaughtException(const QString& detail = QString()); + virtual ScriptContext* currentContext() const; + //virtual ScriptValuePointer evaluate(const QString& program, const QString& fileName = QString()); + virtual ScriptValuePointer evaluate(const ScriptProgramPointer &program); + //virtual ScriptValuePointer evaluateInClosure(const ScriptValuePointer& locals, const ScriptProgramPointer& program); virtual ScriptValuePointer globalObject() const; + virtual bool hasUncaughtException() const; + virtual bool isEvaluating() const; + virtual ScriptValuePointer lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1); + virtual ScriptValuePointer makeError(const ScriptValuePointer& other, const QString& type = "Error"); virtual ScriptManager* manager() const; + + // if there is a pending exception and we are at the top level (non-recursive) stack frame, this emits and resets it + virtual bool maybeEmitUncaughtException(const QString& debugHint = QString()); + virtual ScriptValuePointer newArray(uint length = 0); virtual ScriptValuePointer newArrayBuffer(const QByteArray& message); + virtual ScriptValuePointer newFunction(ScriptEngine::FunctionSignature fun, int length = 0); virtual ScriptValuePointer newObject(); virtual ScriptProgramPointer newProgram(const QString& sourceCode, const QString& fileName); - virtual ScriptValuePointer newQObject(QObject* obj); + virtual ScriptValuePointer newQObject(QObject *object, ScriptEngine::ValueOwnership ownership = ScriptEngine::QtOwnership, + const ScriptEngine::QObjectWrapOptions &options = ScriptEngine::QObjectWrapOptions()); virtual ScriptValuePointer newValue(bool value); virtual ScriptValuePointer newValue(int value); virtual ScriptValuePointer newValue(uint value); @@ -113,8 +104,41 @@ class ScriptEngineQtScript : public BaseScriptEngine, public ScriptEngine { virtual ScriptValuePointer newValue(const char* value); virtual ScriptValuePointer newVariant(const QVariant& value); virtual ScriptValuePointer nullValue(); + virtual bool raiseException(const ScriptValuePointer& exception); + //virtual void registerEnum(const QString& enumName, QMetaEnum newEnum); + virtual void registerFunction(const QString& name, ScriptEngine::FunctionSignature fun, int numArguments = -1); + virtual void registerFunction(const QString& parent, const QString& name, ScriptEngine::FunctionSignature fun, int numArguments = -1); + virtual void registerGetterSetter(const QString& name, ScriptEngine::FunctionSignature getter, ScriptEngine::FunctionSignature setter, const QString& parent = QString("")); + //virtual void registerGlobalObject(const QString& name, QObject* object); virtual void setDefaultPrototype(int metaTypeId, const ScriptValuePointer& prototype); + virtual void setObjectName(const QString& name); + virtual bool setProperty(const char* name, const QVariant& value); + virtual void setProcessEventsInterval(int interval); + virtual QThread* thread() const; virtual ScriptValuePointer undefinedValue(); + virtual ScriptValuePointer uncaughtException() const; + virtual QStringList uncaughtExceptionBacktrace() const; + virtual int uncaughtExceptionLineNumber() const; + + // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways + inline bool IS_THREADSAFE_INVOCATION(const QString& method) { return ScriptEngine::IS_THREADSAFE_INVOCATION(method); } + +protected: // brought over from BaseScriptEngine + /*@jsdoc + * @function Script.makeError + * @param {object} [other] - Other. + * @param {string} [type="Error"] - Error. + * @returns {object} Object. + * @deprecated This function is deprecated and will be removed. + */ + Q_INVOKABLE QScriptValue makeError(const QScriptValue& other = QScriptValue(), const QString& type = "Error"); + + // if the currentContext() is valid then throw the passed exception; otherwise, immediately emit it. + // note: this is used in cases where C++ code might call into JS API methods directly + bool raiseException(const QScriptValue& exception); + + // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways + static bool IS_THREADSAFE_INVOCATION(const QThread* thread, const QString& method); public: ScriptEngineQtScript(ScriptManager* scriptManager = nullptr); @@ -131,7 +155,7 @@ class ScriptEngineQtScript : public BaseScriptEngine, public ScriptEngine { * @deprecated This function is deprecated and will be removed. */ /// registers a global object by name - Q_INVOKABLE void registerGlobalObject(const QString& name, QObject* object); + Q_INVOKABLE virtual void registerGlobalObject(const QString& name, QObject* object); /*@jsdoc * @function Script.registerGetterSetter @@ -176,7 +200,7 @@ class ScriptEngineQtScript : public BaseScriptEngine, public ScriptEngine { // WARNING: This function must be called after a registerGlobalObject that creates the namespace this enum is located in, or // the globalObject won't function. E.g., if you have a Foo object and a Foo.FooType enum, Foo must be registered first. /// registers a global enum - Q_INVOKABLE void registerEnum(const QString& enumName, QMetaEnum newEnum); + Q_INVOKABLE virtual void registerEnum(const QString& enumName, QMetaEnum newEnum); /*@jsdoc * @function Script.registerValue @@ -196,7 +220,7 @@ class ScriptEngineQtScript : public BaseScriptEngine, public ScriptEngine { * @deprecated This function is deprecated and will be removed. */ /// evaluate some code in the context of the ScriptEngineQtScript and return the result - Q_INVOKABLE QScriptValue evaluate(const QString& program, const QString& fileName, int lineNumber = 1); // this is also used by the script tool widget + Q_INVOKABLE virtual ScriptValuePointer evaluate(const QString& program, const QString& fileName, int lineNumber = 1); // this is also used by the script tool widget /*@jsdoc * @function Script.evaluateInClosure @@ -205,7 +229,7 @@ class ScriptEngineQtScript : public BaseScriptEngine, public ScriptEngine { * @returns {object} Object. * @deprecated This function is deprecated and will be removed. */ - Q_INVOKABLE QScriptValue evaluateInClosure(const QScriptValue& locals, const QScriptProgram& program); + Q_INVOKABLE virtual ScriptValuePointer evaluateInClosure(const ScriptValuePointer& locals, const ScriptProgramPointer& program); /*@jsdoc * Checks whether the application was compiled as a debug build. @@ -245,14 +269,12 @@ class ScriptEngineQtScript : public BaseScriptEngine, public ScriptEngine { // NOTE - this is used by the TypedArray implementation. we need to review this for thread safety ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; } -public slots: - /*@jsdoc * @function Script.updateMemoryCost * @param {number} deltaSize - Delta size. * @deprecated This function is deprecated and will be removed. */ - void updateMemoryCost(const qint64&); + virtual void updateMemoryCost(const qint64& deltaSize); signals: @@ -372,6 +394,15 @@ public slots: // script is updated (goes from RUNNING to ERROR_RUNNING_SCRIPT, for example) void entityScriptDetailsUpdated(); +protected: + // like `newFunction`, but allows mapping inline C++ lambdas with captures as callable QScriptValues + // even though the context/engine parameters are redundant in most cases, the function signature matches `newFunction` + // anyway so that newLambdaFunction can be used to rapidly prototype / test utility APIs and then if becoming + // permanent more easily promoted into regular static newFunction scenarios. + QScriptValue newLambdaFunction(std::function operation, + const QScriptValue& data = QScriptValue(), + const QScriptEngine::ValueOwnership& ownership = QScriptEngine::AutoOwnership); + protected: /*@jsdoc @@ -384,6 +415,9 @@ public slots: QPointer _manager; + ScriptValuePointer _nullValue; + ScriptValuePointer _undefinedValue; + std::atomic _isRunning { false }; bool _isThreaded { false }; @@ -394,4 +428,23 @@ public slots: ArrayBufferClass* _arrayBufferClass; }; -#endif // hifi_ScriptEngineQtScript_h +// Lambda helps create callable QScriptValues out of std::functions: +// (just meant for use from within the script engine itself) +class Lambda : public QObject { + Q_OBJECT +public: + Lambda(ScriptEngineQtScript* engine, + std::function operation, + QScriptValue data); + ~Lambda(); +public slots: + QScriptValue call(); + QString toString() const; + +private: + ScriptEngineQtScript* engine; + std::function operation; + QScriptValue data; +}; + +#endif // hifi_ScriptEngineQtScript_h diff --git a/libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.h b/libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.h new file mode 100644 index 00000000000..46ba129579e --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.h @@ -0,0 +1,40 @@ +// +// ScriptProgramQtWrapper.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/21/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ScriptProgramQtWrapper_h +#define hifi_ScriptProgramQtWrapper_h + +#include +#include + +#include "../ScriptProgram.h" +#include "ScriptEngineQtScript.h" + +class ScriptProgramQtWrapper : public ScriptProgram { +public: // construction + inline ScriptProgramQtWrapper(ScriptEngineQtScript* engine, const QScriptProgram& value) : + _engine(engine), _value(value) {} + inline ScriptProgramQtWrapper(ScriptEngineQtScript* engine, QScriptProgram&& value) : + _engine(engine), _value(std::move(value)) {} + static ScriptProgramQtWrapper* unwrap(ScriptProgramPointer val); + inline const QScriptProgram& toQtValue() const { return _value; } + +public: // ScriptProgram implementation + virtual ScriptSyntaxCheckResultPointer checkSyntax() const; + virtual QString fileName() const; + virtual QString sourceCode() const; + +private: // storage + QPointer _engine; + QScriptProgram _value; +}; + +#endif // hifi_ScriptValueQtWrapper_h \ No newline at end of file diff --git a/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.cpp b/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.cpp new file mode 100644 index 00000000000..d6f7a200d72 --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.cpp @@ -0,0 +1,196 @@ +// +// ScriptValueQtWrapper.cpp +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/16/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptValueQtWrapper.h" + +ScriptValueQtWrapper* ScriptValueQtWrapper::unwrap(ScriptValuePointer val) { + if (!val) { + return nullptr; + } + + return dynamic_cast(val.data()); +} + +QScriptValue ScriptValueQtWrapper::fullUnwrap(const ScriptValuePointer& value) const { + if (!value) { + return QScriptValue(); + } + ScriptValueQtWrapper* unwrapped = unwrap(value); + if (unwrapped) { + return unwrapped->toQtValue(); + } + return _engine->newVariant(value->toVariant()); +} + +//ScriptValuePointer ScriptValueQtWrapper::call(const ScriptValuePointer& thisObject, const ScriptValueList& args); + +ScriptValuePointer ScriptValueQtWrapper::call(const ScriptValuePointer& thisObject, const ScriptValuePointer& arguments) { + QScriptValue empty; + + ScriptValueQtWrapper* unwrappedThis = unwrap(thisObject); + if (thisObject && !unwrappedThis) { + return _engine->undefinedValue(); + } + const QScriptValue& qThis = unwrappedThis ? unwrappedThis->toQtValue() : empty; + + ScriptValueQtWrapper* unwrappedArgs = unwrap(arguments); + if (arguments && !unwrappedArgs) { + return _engine->undefinedValue(); + } + const QScriptValue& qArgs = unwrappedArgs ? unwrappedArgs->toQtValue() : empty; + + QScriptValue result = _value.call(qThis, qArgs); + return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +//ScriptValuePointer ScriptValueQtWrapper::construct(const ScriptValueList& args = ScriptValueList()); + +ScriptValuePointer ScriptValueQtWrapper::construct(const ScriptValuePointer& arguments) { + ScriptValueQtWrapper* unwrapped = unwrap(arguments); + QScriptValue result = _value.construct(unwrapped ? unwrapped->toQtValue() : QScriptValue()); + return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +ScriptValuePointer ScriptValueQtWrapper::data() const { + QScriptValue result = _value.data(); + return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +ScriptEnginePointer ScriptValueQtWrapper::engine() const { + if (!_engine) { + return ScriptEnginePointer(); + } + return _engine->sharedFromThis(); +} + +//ScriptValueIteratorPointer ScriptValueQtWrapper::newIterator(); + +ScriptValuePointer ScriptValueQtWrapper::property(const QString& name, const ResolveFlags& mode) const { + QScriptValue result = _value.property(name, (QScriptValue::ResolveFlags)(int)mode); + return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +ScriptValuePointer ScriptValueQtWrapper::property(quint32 arrayIndex, const ResolveFlags& mode) const { + QScriptValue result = _value.property(arrayIndex, (QScriptValue::ResolveFlags)(int)mode); + return ScriptValuePointer(new ScriptValueQtWrapper(_engine, std::move(result))); +} + +void ScriptValueQtWrapper::setData(const ScriptValuePointer& value) { + QScriptValue unwrapped = fullUnwrap(value); + _value.setData(unwrapped); +} + +void ScriptValueQtWrapper::setProperty(const QString& name, const ScriptValuePointer& value, const PropertyFlags& flags) { + QScriptValue unwrapped = fullUnwrap(value); + _value.setProperty(name, unwrapped, (QScriptValue::PropertyFlags)(int)flags); +} + +void ScriptValueQtWrapper::setProperty(quint32 arrayIndex, const ScriptValuePointer& value, const PropertyFlags& flags) { + QScriptValue unwrapped = fullUnwrap(value); + _value.setProperty(arrayIndex, unwrapped, (QScriptValue::PropertyFlags)(int)flags); +} + +void ScriptValueQtWrapper::setPrototype(const ScriptValuePointer& prototype) { + ScriptValueQtWrapper* unwrappedPrototype = unwrap(prototype); + if (unwrappedPrototype) { + _value.setPrototype(unwrappedPrototype->toQtValue()); + } +} + +bool ScriptValueQtWrapper::strictlyEquals(const ScriptValuePointer& other) const { + ScriptValueQtWrapper* unwrappedOther = unwrap(other); + return unwrappedOther ? _value.strictlyEquals(unwrappedOther->toQtValue()) : false; +} + +bool ScriptValueQtWrapper::toBool() const { + return _value.toBool(); +} + +qint32 ScriptValueQtWrapper::toInt32() const { + return _value.toInt32(); +} + +double ScriptValueQtWrapper::toInteger() const { + return _value.toInteger(); +} + +double ScriptValueQtWrapper::toNumber() const { + return _value.toNumber(); +} + +QString ScriptValueQtWrapper::toString() const { + return _value.toString(); +} + +quint16 ScriptValueQtWrapper::toUInt16() const { + return _value.toUInt16(); +} + +quint32 ScriptValueQtWrapper::toUInt32() const { + return _value.toUInt32(); +} + +QVariant ScriptValueQtWrapper::toVariant() const { + return _value.toVariant(); +} + +QObject* ScriptValueQtWrapper::toQObject() const { + return _value.toQObject(); +} + +bool ScriptValueQtWrapper::equalsInternal(const ScriptValuePointer& other) const { + ScriptValueQtWrapper* unwrappedOther = unwrap(other); + return unwrappedOther ? _value.equals(unwrappedOther->toQtValue()) : false; +} + +bool ScriptValueQtWrapper::isArrayInternal() const { + return _value.isArray(); +} + +bool ScriptValueQtWrapper::isBoolInternal() const { + return _value.isBool(); +} + +bool ScriptValueQtWrapper::isErrorInternal() const { + return _value.isError(); +} + +bool ScriptValueQtWrapper::isFunctionInternal() const { + return _value.isFunction(); +} + +bool ScriptValueQtWrapper::isNumberInternal() const { + return _value.isNumber(); +} + +bool ScriptValueQtWrapper::isNullInternal() const { + return _value.isNull(); +} + +bool ScriptValueQtWrapper::isObjectInternal() const { + return _value.isObject(); +} + +bool ScriptValueQtWrapper::isStringInternal() const { + return _value.isString(); +} + +bool ScriptValueQtWrapper::isUndefinedInternal() const { + return _value.isUndefined(); +} + +bool ScriptValueQtWrapper::isValidInternal() const { + return _value.isValid(); +} + +bool ScriptValueQtWrapper::isVariantInternal() const { + return _value.isVariant(); +} diff --git a/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.h b/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.h new file mode 100644 index 00000000000..104a7a28081 --- /dev/null +++ b/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.h @@ -0,0 +1,85 @@ +// +// ScriptValueQtWrapper.h +// libraries/script-engine/src +// +// Created by Heather Anderson on 5/16/21. +// Copyright 2021 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ScriptValueQtWrapper_h +#define hifi_ScriptValueQtWrapper_h + +#include +#include + +#include + +#include "../ScriptValue.h" +#include "ScriptEngineQtScript.h" + +class ScriptValueQtWrapper : public ScriptValue { +public: // construction + inline ScriptValueQtWrapper(ScriptEngineQtScript* engine, const QScriptValue& value) : + _engine(engine), _value(value) {} + inline ScriptValueQtWrapper(ScriptEngineQtScript* engine, QScriptValue&& value) : + _engine(engine), _value(std::move(value)) {} + static ScriptValueQtWrapper* unwrap(ScriptValuePointer val); + inline const QScriptValue& toQtValue() const { return _value; } + +public: // ScriptValue implementation + virtual ScriptValuePointer call(const ScriptValuePointer& thisObject = ScriptValuePointer(), + const ScriptValueList& args = ScriptValueList()); + virtual ScriptValuePointer call(const ScriptValuePointer& thisObject, const ScriptValuePointer& arguments); + virtual ScriptValuePointer construct(const ScriptValueList& args = ScriptValueList()); + virtual ScriptValuePointer construct(const ScriptValuePointer& arguments); + virtual ScriptValuePointer data() const; + virtual ScriptEnginePointer engine() const; + virtual ScriptValueIteratorPointer newIterator(); + virtual ScriptValuePointer property(const QString& name, const ResolveFlags& mode = ResolvePrototype) const; + virtual ScriptValuePointer property(quint32 arrayIndex, const ResolveFlags& mode = ResolvePrototype) const; + virtual void setData(const ScriptValuePointer& val); + virtual void setProperty(const QString& name, + const ScriptValuePointer& value, + const PropertyFlags& flags = KeepExistingFlags); + virtual void setProperty(quint32 arrayIndex, + const ScriptValuePointer& value, + const PropertyFlags& flags = KeepExistingFlags); + virtual void setPrototype(const ScriptValuePointer& prototype); + virtual bool strictlyEquals(const ScriptValuePointer& other) const; + + virtual bool toBool() const; + virtual qint32 toInt32() const; + virtual double toInteger() const; + virtual double toNumber() const; + virtual QString toString() const; + virtual quint16 toUInt16() const; + virtual quint32 toUInt32() const; + virtual QVariant toVariant() const; + virtual QObject* toQObject() const; + +protected: // ScriptValue implementation + virtual bool equalsInternal(const ScriptValuePointer& other) const; + virtual bool isArrayInternal() const; + virtual bool isBoolInternal() const; + virtual bool isErrorInternal() const; + virtual bool isFunctionInternal() const; + virtual bool isNumberInternal() const; + virtual bool isNullInternal() const; + virtual bool isObjectInternal() const; + virtual bool isStringInternal() const; + virtual bool isUndefinedInternal() const; + virtual bool isValidInternal() const; + virtual bool isVariantInternal() const; + +private: // helper functions + QScriptValue fullUnwrap(const ScriptValuePointer& value) const; + +private: // storage + QPointer _engine; + QScriptValue _value; +}; + +#endif // hifi_ScriptValueQtWrapper_h \ No newline at end of file diff --git a/libraries/script-engine-qtscript/src/TypedArrayPrototype.cpp b/libraries/script-engine/src/qtscript/TypedArrayPrototype.cpp similarity index 100% rename from libraries/script-engine-qtscript/src/TypedArrayPrototype.cpp rename to libraries/script-engine/src/qtscript/TypedArrayPrototype.cpp diff --git a/libraries/script-engine-qtscript/src/TypedArrayPrototype.h b/libraries/script-engine/src/qtscript/TypedArrayPrototype.h similarity index 100% rename from libraries/script-engine-qtscript/src/TypedArrayPrototype.h rename to libraries/script-engine/src/qtscript/TypedArrayPrototype.h diff --git a/libraries/script-engine-qtscript/src/TypedArrays.cpp b/libraries/script-engine/src/qtscript/TypedArrays.cpp similarity index 100% rename from libraries/script-engine-qtscript/src/TypedArrays.cpp rename to libraries/script-engine/src/qtscript/TypedArrays.cpp diff --git a/libraries/script-engine-qtscript/src/TypedArrays.h b/libraries/script-engine/src/qtscript/TypedArrays.h similarity index 100% rename from libraries/script-engine-qtscript/src/TypedArrays.h rename to libraries/script-engine/src/qtscript/TypedArrays.h