From 5769e89cffe42325532fef260d3e47761ce31539 Mon Sep 17 00:00:00 2001 From: ksuprynowicz Date: Sat, 8 Oct 2022 12:05:27 +0200 Subject: [PATCH] Initial V8 support, not working yet --- .../src/qtscript/ArrayBufferClass.h | 66 - .../src/qtscript/ArrayBufferViewClass.h | 58 - .../src/qtscript/ScriptContextQtWrapper.cpp | 92 -- .../src/qtscript/ScriptEngineQtScript.cpp | 919 -------------- .../src/qtscript/ScriptObjectQtProxy.cpp | 794 ------------ .../src/qtscript/ScriptObjectQtProxy.h | 213 ---- .../src/qtscript/ScriptProgramQtWrapper.cpp | 55 - .../src/qtscript/ScriptProgramQtWrapper.h | 61 - .../qtscript/ScriptValueIteratorQtWrapper.cpp | 33 - .../qtscript/ScriptValueIteratorQtWrapper.h | 47 - .../src/qtscript/ScriptValueQtWrapper.cpp | 234 ---- .../script-engine/src/qtscript/TypedArrays.h | 154 --- .../src/{qtscript => v8}/ArrayBufferClass.cpp | 63 +- .../script-engine/src/v8/ArrayBufferClass.h | 66 + .../{qtscript => v8}/ArrayBufferPrototype.cpp | 8 +- .../{qtscript => v8}/ArrayBufferPrototype.h | 12 +- .../{qtscript => v8}/ArrayBufferViewClass.cpp | 23 +- .../src/v8/ArrayBufferViewClass.h | 54 + .../src/{qtscript => v8}/DataViewClass.cpp | 32 +- .../src/{qtscript => v8}/DataViewClass.h | 20 +- .../{qtscript => v8}/DataViewPrototype.cpp | 25 +- .../src/{qtscript => v8}/DataViewPrototype.h | 15 +- .../src/v8/ScriptContextV8Wrapper.cpp | 135 ++ .../ScriptContextV8Wrapper.h} | 44 +- .../script-engine/src/v8/ScriptEngineV8.cpp | 1093 ++++++++++++++++ .../ScriptEngineV8.h} | 120 +- .../ScriptEngineV8_cast.cpp} | 206 +-- .../src/v8/ScriptObjectV8Proxy.cpp | 1105 +++++++++++++++++ .../src/v8/ScriptObjectV8Proxy.h | 245 ++++ .../src/v8/ScriptProgramV8Wrapper.cpp | 64 + .../src/v8/ScriptProgramV8Wrapper.h | 76 ++ .../src/v8/ScriptValueIteratorV8Wrapper.cpp | 82 ++ .../src/v8/ScriptValueIteratorV8Wrapper.h | 64 + .../src/v8/ScriptValueV8Wrapper.cpp | 367 ++++++ .../ScriptValueV8Wrapper.h} | 36 +- .../{qtscript => v8}/TypedArrayPrototype.cpp | 28 +- .../{qtscript => v8}/TypedArrayPrototype.h | 22 +- .../src/{qtscript => v8}/TypedArrays.cpp | 163 +-- libraries/script-engine/src/v8/TypedArrays.h | 155 +++ libraries/script-engine/src/v8/V8Types.h | 76 ++ 40 files changed, 4039 insertions(+), 3086 deletions(-) delete mode 100644 libraries/script-engine/src/qtscript/ArrayBufferClass.h delete mode 100644 libraries/script-engine/src/qtscript/ArrayBufferViewClass.h delete mode 100644 libraries/script-engine/src/qtscript/ScriptContextQtWrapper.cpp delete mode 100644 libraries/script-engine/src/qtscript/ScriptEngineQtScript.cpp delete mode 100644 libraries/script-engine/src/qtscript/ScriptObjectQtProxy.cpp delete mode 100644 libraries/script-engine/src/qtscript/ScriptObjectQtProxy.h delete mode 100644 libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.cpp delete mode 100644 libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.h delete mode 100644 libraries/script-engine/src/qtscript/ScriptValueIteratorQtWrapper.cpp delete mode 100644 libraries/script-engine/src/qtscript/ScriptValueIteratorQtWrapper.h delete mode 100644 libraries/script-engine/src/qtscript/ScriptValueQtWrapper.cpp delete mode 100644 libraries/script-engine/src/qtscript/TypedArrays.h rename libraries/script-engine/src/{qtscript => v8}/ArrayBufferClass.cpp (72%) create mode 100644 libraries/script-engine/src/v8/ArrayBufferClass.h rename libraries/script-engine/src/{qtscript => v8}/ArrayBufferPrototype.cpp (96%) rename libraries/script-engine/src/{qtscript => v8}/ArrayBufferPrototype.h (70%) rename libraries/script-engine/src/{qtscript => v8}/ArrayBufferViewClass.cpp (74%) create mode 100644 libraries/script-engine/src/v8/ArrayBufferViewClass.h rename libraries/script-engine/src/{qtscript => v8}/DataViewClass.cpp (72%) rename libraries/script-engine/src/{qtscript => v8}/DataViewClass.h (50%) rename libraries/script-engine/src/{qtscript => v8}/DataViewPrototype.cpp (94%) rename libraries/script-engine/src/{qtscript => v8}/DataViewPrototype.h (84%) create mode 100644 libraries/script-engine/src/v8/ScriptContextV8Wrapper.cpp rename libraries/script-engine/src/{qtscript/ScriptContextQtWrapper.h => v8/ScriptContextV8Wrapper.h} (52%) create mode 100644 libraries/script-engine/src/v8/ScriptEngineV8.cpp rename libraries/script-engine/src/{qtscript/ScriptEngineQtScript.h => v8/ScriptEngineV8.h} (66%) rename libraries/script-engine/src/{qtscript/ScriptEngineQtScript_cast.cpp => v8/ScriptEngineV8_cast.cpp} (68%) create mode 100644 libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp create mode 100644 libraries/script-engine/src/v8/ScriptObjectV8Proxy.h create mode 100644 libraries/script-engine/src/v8/ScriptProgramV8Wrapper.cpp create mode 100644 libraries/script-engine/src/v8/ScriptProgramV8Wrapper.h create mode 100644 libraries/script-engine/src/v8/ScriptValueIteratorV8Wrapper.cpp create mode 100644 libraries/script-engine/src/v8/ScriptValueIteratorV8Wrapper.h create mode 100644 libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp rename libraries/script-engine/src/{qtscript/ScriptValueQtWrapper.h => v8/ScriptValueV8Wrapper.h} (76%) rename libraries/script-engine/src/{qtscript => v8}/TypedArrayPrototype.cpp (81%) rename libraries/script-engine/src/{qtscript => v8}/TypedArrayPrototype.h (50%) rename libraries/script-engine/src/{qtscript => v8}/TypedArrays.cpp (66%) create mode 100644 libraries/script-engine/src/v8/TypedArrays.h create mode 100644 libraries/script-engine/src/v8/V8Types.h diff --git a/libraries/script-engine/src/qtscript/ArrayBufferClass.h b/libraries/script-engine/src/qtscript/ArrayBufferClass.h deleted file mode 100644 index c9efce1ad4..0000000000 --- a/libraries/script-engine/src/qtscript/ArrayBufferClass.h +++ /dev/null @@ -1,66 +0,0 @@ -// -// ArrayBufferClass.h -// -// -// 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 -// - -/// @addtogroup ScriptEngine -/// @{ - -#ifndef hifi_ArrayBufferClass_h -#define hifi_ArrayBufferClass_h - -#include -#include -#include -#include -#include -#include -#include -#include - -class ScriptEngineQtScript; - -/// [QtScript] Implements the ArrayBuffer scripting class -class ArrayBufferClass : public QObject, public QScriptClass { - Q_OBJECT -public: - ArrayBufferClass(ScriptEngineQtScript* scriptEngine); - QScriptValue newInstance(qint32 size); - QScriptValue newInstance(const QByteArray& ba); - - QueryFlags queryProperty(const QScriptValue& object, - const QScriptString& name, - QueryFlags flags, uint* id) override; - QScriptValue property(const QScriptValue& object, - const QScriptString& name, uint id) override; - QScriptValue::PropertyFlags propertyFlags(const QScriptValue& object, - const QScriptString& name, uint id) override; - - QString name() const override; - QScriptValue prototype() const override; - - -private: - static QScriptValue construct(QScriptContext* context, QScriptEngine* engine); - - static QScriptValue toScriptValue(QScriptEngine* eng, const QByteArray& ba); - static void fromScriptValue(const QScriptValue& obj, QByteArray& ba); - - QScriptValue _proto; - QScriptValue _ctor; - - // JS Object attributes - QScriptString _name; - QScriptString _byteLength; - -}; - -#endif // hifi_ArrayBufferClass_h - -/// @} diff --git a/libraries/script-engine/src/qtscript/ArrayBufferViewClass.h b/libraries/script-engine/src/qtscript/ArrayBufferViewClass.h deleted file mode 100644 index e5c40d8e44..0000000000 --- a/libraries/script-engine/src/qtscript/ArrayBufferViewClass.h +++ /dev/null @@ -1,58 +0,0 @@ -// -// ArrayBufferViewClass.h -// -// -// Created by Clement on 7/8/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 -// - -/// @addtogroup ScriptEngine -/// @{ - -#ifndef hifi_ArrayBufferViewClass_h -#define hifi_ArrayBufferViewClass_h - -#include -#include -#include -#include -#include -#include -#include - -class ScriptEngineQtScript; - -static const QString BUFFER_PROPERTY_NAME = "buffer"; -static const QString BYTE_OFFSET_PROPERTY_NAME = "byteOffset"; -static const QString BYTE_LENGTH_PROPERTY_NAME = "byteLength"; - -/// [QtScript] The base class containing common code for ArrayBuffer views -class ArrayBufferViewClass : public QObject, public QScriptClass { - Q_OBJECT -public: - ArrayBufferViewClass(ScriptEngineQtScript* scriptEngine); - - ScriptEngineQtScript* getScriptEngine() { return _scriptEngine; } - - virtual QueryFlags queryProperty(const QScriptValue& object, - const QScriptString& name, - QueryFlags flags, uint* id) override; - virtual QScriptValue property(const QScriptValue& object, - const QScriptString& name, uint id) override; - virtual QScriptValue::PropertyFlags propertyFlags(const QScriptValue& object, - const QScriptString& name, uint id) override; -protected: - // JS Object attributes - QScriptString _bufferName; - QScriptString _byteOffsetName; - QScriptString _byteLengthName; - - ScriptEngineQtScript* _scriptEngine; -}; - -#endif // hifi_ArrayBufferViewClass_h - -/// @} diff --git a/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.cpp b/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.cpp deleted file mode 100644 index 72f6ea41b1..0000000000 --- a/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// -// ScriptContextQtWrapper.cpp -// libraries/script-engine/src/qtscript -// -// 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(); -} - -ScriptValue ScriptContextQtWrapper::argument(int index) const { - QScriptValue result = _context->argument(index); - return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); -} - -QStringList ScriptContextQtWrapper::backtrace() const { - return _context->backtrace(); -} - -ScriptValue ScriptContextQtWrapper::callee() const { - QScriptValue result = _context->callee(); - return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); -} - -ScriptEnginePointer ScriptContextQtWrapper::engine() const { - return _engine->shared_from_this(); -} - -ScriptFunctionContextPointer ScriptContextQtWrapper::functionContext() const { - return std::make_shared(_context); -} - -ScriptContextPointer ScriptContextQtWrapper::parentContext() const { - QScriptContext* result = _context->parentContext(); - return result ? std::make_shared(_engine, result) : ScriptContextPointer(); -} - -ScriptValue ScriptContextQtWrapper::thisObject() const { - QScriptValue result = _context->thisObject(); - return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); -} - -ScriptValue ScriptContextQtWrapper::throwError(const QString& text) { - QScriptValue result = _context->throwError(text); - return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); -} - -ScriptValue ScriptContextQtWrapper::throwValue(const ScriptValue& value) { - ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(value); - if (!unwrapped) { - return _engine->undefinedValue(); - } - QScriptValue result = _context->throwValue(unwrapped->toQtValue()); - return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); -} - - -QString ScriptFunctionContextQtWrapper::fileName() const { - return _value.fileName(); -} - -QString ScriptFunctionContextQtWrapper::functionName() const { - return _value.functionName(); -} - -ScriptFunctionContext::FunctionType ScriptFunctionContextQtWrapper::functionType() const { - return static_cast(_value.functionType()); -} - -int ScriptFunctionContextQtWrapper::lineNumber() const { - return _value.lineNumber(); -} diff --git a/libraries/script-engine/src/qtscript/ScriptEngineQtScript.cpp b/libraries/script-engine/src/qtscript/ScriptEngineQtScript.cpp deleted file mode 100644 index 7b4a39f942..0000000000 --- a/libraries/script-engine/src/qtscript/ScriptEngineQtScript.cpp +++ /dev/null @@ -1,919 +0,0 @@ -// -// ScriptEngineQtScript.cpp -// libraries/script-engine/src/qtscript -// -// 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 "ScriptContextQtWrapper.h" -#include "ScriptObjectQtProxy.h" -#include "ScriptProgramQtWrapper.h" -#include "ScriptValueQtWrapper.h" - -static const int MAX_DEBUG_VALUE_LENGTH { 80 }; - -bool ScriptEngineQtScript::IS_THREADSAFE_INVOCATION(const QThread* thread, const QString& method) { - const QThread* currentThread = QThread::currentThread(); - if (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 QScriptEngine::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; -} - -ScriptValue ScriptEngineQtScript::makeError(const ScriptValue& _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 ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); -} - -// check syntax and when there are issues returns an actual "SyntaxError" with the details -ScriptValue 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 ScriptValue(new ScriptValueQtWrapper(this, std::move(err))); - } - return undefinedValue(); -} - -// this pulls from the best available information to create a detailed snapshot of the current exception -ScriptValue 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 (_scriptManager) { - // 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 _scriptManager->unhandledException(ScriptValue(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() && _scriptManager) { - emit _scriptManager->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 static_cast(engine)->nullValue(); - } - return operation(static_cast(engine)->currentContext(), engine); -} - -#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 - -ScriptEngineQtScript::ScriptEngineQtScript(ScriptManager* scriptManager) : - QScriptEngine(), - _scriptManager(scriptManager), - _arrayBufferClass(new ArrayBufferClass(this)) -{ - registerSystemTypes(); - - if (_scriptManager) { - connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) { - if (hasUncaughtException()) { - // the engine's uncaughtException() seems to produce much better stack traces here - emit _scriptManager->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 _scriptManager->unhandledException(ScriptValue(new ScriptValueQtWrapper(this, std::move(thrown)))); - } - }, Qt::DirectConnection); - moveToThread(scriptManager->thread()); - } - - QScriptValue null = QScriptEngine::nullValue(); - _nullValue = ScriptValue(new ScriptValueQtWrapper(this, std::move(null))); - - QScriptValue undefined = QScriptEngine::undefinedValue(); - _undefinedValue = ScriptValue(new ScriptValueQtWrapper(this, std::move(undefined))); - - QScriptEngine::setProcessEventsInterval(MSECS_PER_SECOND); -} - -void ScriptEngineQtScript::registerEnum(const QString& enumName, QMetaEnum newEnum) { - if (!newEnum.isValid()) { - qCCritical(scriptengine) << "registerEnum called on invalid enum with name " << enumName; - return; - } - - for (int i = 0; i < newEnum.keyCount(); i++) { - const char* keyName = newEnum.key(i); - QString fullName = enumName + "." + keyName; - registerValue(fullName, newEnum.keyToValue(keyName)); - } -} - -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 = QScriptEngine::globalObject(); - - for (const auto& pathPart : pathToValue) { - partsToGo--; - if (!partObject.property(pathPart).isValid()) { - if (partsToGo > 0) { - //QObject *object = new QObject; - QScriptValue partValue = QScriptEngine::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 = ScriptObjectQtProxy::newQObject(this, object, ScriptEngine::QtOwnership); - 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& name, ScriptEngine::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(ScriptEngine::FunctionSignature, functionSignature), - Q_ARG(int, numArguments)); - return; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name; -#endif - - auto scriptFun = newFunction(functionSignature, numArguments); - 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::registerFunction(const QString& parent, const QString& name, ScriptEngine::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(ScriptEngine::FunctionSignature, functionSignature), - Q_ARG(int, numArguments)); - return; - } -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "ScriptEngineQtScript::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name; -#endif - - auto object = globalObject().property(parent); - if (object.isValid()) { - auto scriptFun = 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); - } -} - -void ScriptEngineQtScript::registerGetterSetter(const QString& name, ScriptEngine::FunctionSignature getter, - ScriptEngine::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(ScriptEngine::FunctionSignature, getter), - Q_ARG(ScriptEngine::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 - - auto setterFunction = newFunction(setter, 1); - auto getterFunction = newFunction(getter); - - if (!parent.isNull() && !parent.isEmpty()) { - auto object = globalObject().property(parent); - if (object.isValid()) { - object.setProperty(name, setterFunction, ScriptValue::PropertySetter); - object.setProperty(name, getterFunction, ScriptValue::PropertyGetter); - } - } else { - globalObject().setProperty(name, setterFunction, ScriptValue::PropertySetter); - globalObject().setProperty(name, getterFunction, ScriptValue::PropertyGetter); - } -} - -ScriptValue ScriptEngineQtScript::evaluateInClosure(const ScriptValue& _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 - ScriptValue 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 = ScriptValue(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; -} - -ScriptValue ScriptEngineQtScript::evaluate(const QString& sourceCode, const QString& fileName) { - if (_scriptManager && _scriptManager->isStopped()) { - return undefinedValue(); // bail early - } - - if (QThread::currentThread() != QScriptEngine::thread()) { - ScriptValue result; -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - "sourceCode:" << sourceCode << " fileName:" << fileName; -#endif - BLOCKING_INVOKE_METHOD(this, "evaluate", - Q_RETURN_ARG(ScriptValue, result), - Q_ARG(const QString&, sourceCode), - Q_ARG(const QString&, fileName)); - 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, 1 }; - if (program.isNull()) { - // can this happen? - auto err = makeError(newValue("could not create QScriptProgram for " + fileName)); - raiseException(err); - maybeEmitUncaughtException("compile"); - return err; - } - - QScriptValue result = QScriptEngine::evaluate(program); - maybeEmitUncaughtException("evaluate"); - - return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); -} - -Q_INVOKABLE ScriptValue ScriptEngineQtScript::evaluate(const ScriptProgramPointer& program) { - if (_scriptManager && _scriptManager->isStopped()) { - return undefinedValue(); // bail early - } - - if (QThread::currentThread() != QScriptEngine::thread()) { - ScriptValue result; -#ifdef THREAD_DEBUGGING - qCDebug(scriptengine) << "*** WARNING *** ScriptEngineQtScript::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " - "sourceCode:" << sourceCode << " fileName:" << fileName; -#endif - BLOCKING_INVOKE_METHOD(this, "evaluate", - Q_RETURN_ARG(ScriptValue, result), - Q_ARG(const ScriptProgramPointer&, program)); - return result; - } - - ScriptProgramQtWrapper* unwrapped = ScriptProgramQtWrapper::unwrap(program); - if (!unwrapped) { - auto err = makeError(newValue("could not unwrap program")); - raiseException(err); - maybeEmitUncaughtException("compile"); - return err; - } - - const QScriptProgram& qProgram = unwrapped->toQtValue(); - if (qProgram.isNull()) { - // can this happen? - auto err = makeError(newValue("requested program is empty")); - raiseException(err); - maybeEmitUncaughtException("compile"); - return err; - } - - QScriptValue result = QScriptEngine::evaluate(qProgram); - maybeEmitUncaughtException("evaluate"); - - return ScriptValue(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 - } -} - - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ScriptEngine implementation - -ScriptValue ScriptEngineQtScript::globalObject() const { - QScriptValue global = QScriptEngine::globalObject(); // can't cache the value as it may change - return ScriptValue(new ScriptValueQtWrapper(const_cast(this), std::move(global))); -} - -ScriptManager* ScriptEngineQtScript::manager() const { - return _scriptManager; -} - -ScriptValue ScriptEngineQtScript::newArray(uint length) { - QScriptValue result = QScriptEngine::newArray(length); - return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); -} - -ScriptValue 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 ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); -} - -ScriptValue ScriptEngineQtScript::newObject() { - QScriptValue result = QScriptEngine::newObject(); - return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); -} - -ScriptProgramPointer ScriptEngineQtScript::newProgram(const QString& sourceCode, const QString& fileName) { - QScriptProgram result(sourceCode, fileName); - return std::make_shared(this, result); -} - -ScriptValue ScriptEngineQtScript::newQObject(QObject* object, - ScriptEngine::ValueOwnership ownership, - const ScriptEngine::QObjectWrapOptions& options) { - QScriptValue result = ScriptObjectQtProxy::newQObject(this, object, ownership, options); - return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); -} - -ScriptValue ScriptEngineQtScript::newValue(bool value) { - QScriptValue result(this, value); - return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); -} - -ScriptValue ScriptEngineQtScript::newValue(int value) { - QScriptValue result(this, value); - return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); -} - -ScriptValue ScriptEngineQtScript::newValue(uint value) { - QScriptValue result(this, value); - return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); -} - -ScriptValue ScriptEngineQtScript::newValue(double value) { - QScriptValue result(this, value); - return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); -} - -ScriptValue ScriptEngineQtScript::newValue(const QString& value) { - QScriptValue result(this, value); - return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); -} - -ScriptValue ScriptEngineQtScript::newValue(const QLatin1String& value) { - QScriptValue result(this, value); - return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); -} - -ScriptValue ScriptEngineQtScript::newValue(const char* value) { - QScriptValue result(this, value); - return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); -} - -ScriptValue ScriptEngineQtScript::newVariant(const QVariant& value) { - QScriptValue result = castVariantToValue(value); - return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); -} - -ScriptValue ScriptEngineQtScript::nullValue() { - return _nullValue; -} - -ScriptValue ScriptEngineQtScript::undefinedValue() { - return _undefinedValue; -} - -void ScriptEngineQtScript::abortEvaluation() { - QScriptEngine::abortEvaluation(); -} - -void ScriptEngineQtScript::clearExceptions() { - QScriptEngine::clearExceptions(); -} - -ScriptContext* ScriptEngineQtScript::currentContext() const { - QScriptContext* localCtx = QScriptEngine::currentContext(); - if (!localCtx) { - return nullptr; - } - if (!_currContext || _currContext->toQtValue() != localCtx) { - _currContext = std::make_shared(const_cast(this), localCtx); - } - return _currContext.get(); -} - -bool ScriptEngineQtScript::hasUncaughtException() const { - return QScriptEngine::hasUncaughtException(); -} - -bool ScriptEngineQtScript::isEvaluating() const { - return QScriptEngine::isEvaluating(); -} - -ScriptValue ScriptEngineQtScript::newFunction(ScriptEngine::FunctionSignature fun, int length) { - auto innerFunc = [](QScriptContext* _context, QScriptEngine* _engine) -> QScriptValue { - auto callee = _context->callee(); - QVariant funAddr = callee.property("_func").toVariant(); - ScriptEngine::FunctionSignature fun = reinterpret_cast(funAddr.toULongLong()); - ScriptEngineQtScript* engine = static_cast(_engine); - ScriptContextQtWrapper context(engine, _context); - ScriptValue result = fun(&context, engine); - ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(result); - return unwrapped ? unwrapped->toQtValue() : QScriptValue(); - }; - - QScriptValue result = QScriptEngine::newFunction(innerFunc, length); - auto funAddr = QScriptEngine::newVariant(QVariant(reinterpret_cast(fun))); - result.setProperty("_func", funAddr, QScriptValue::PropertyFlags(QScriptValue::ReadOnly + QScriptValue::Undeletable + QScriptValue::SkipInEnumeration)); - return ScriptValue(new ScriptValueQtWrapper(this, std::move(result))); -} - -void ScriptEngineQtScript::setObjectName(const QString& name) { - QScriptEngine::setObjectName(name); -} - -bool ScriptEngineQtScript::setProperty(const char* name, const QVariant& value) { - return QScriptEngine::setProperty(name, value); -} - -void ScriptEngineQtScript::setProcessEventsInterval(int interval) { - QScriptEngine::setProcessEventsInterval(interval); -} - -QThread* ScriptEngineQtScript::thread() const { - return QScriptEngine::thread(); -} - -void ScriptEngineQtScript::setThread(QThread* thread) { - moveToThread(thread); -} - -ScriptValue ScriptEngineQtScript::uncaughtException() const { - QScriptValue result = QScriptEngine::uncaughtException(); - return ScriptValue(new ScriptValueQtWrapper(const_cast(this), std::move(result))); -} - -QStringList ScriptEngineQtScript::uncaughtExceptionBacktrace() const { - return QScriptEngine::uncaughtExceptionBacktrace(); -} - -int ScriptEngineQtScript::uncaughtExceptionLineNumber() const { - return QScriptEngine::uncaughtExceptionLineNumber(); -} - -bool ScriptEngineQtScript::raiseException(const ScriptValue& exception) { - ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(exception); - QScriptValue qException = unwrapped ? unwrapped->toQtValue() : QScriptEngine::newVariant(exception.toVariant()); - return raiseException(qException); -} - -ScriptValue ScriptEngineQtScript::create(int type, const void* ptr) { - QVariant variant(type, ptr); - QScriptValue scriptValue = castVariantToValue(variant); - return ScriptValue(new ScriptValueQtWrapper(this, std::move(scriptValue))); -} - -QVariant ScriptEngineQtScript::convert(const ScriptValue& value, int typeId) { - ScriptValueQtWrapper* unwrapped = ScriptValueQtWrapper::unwrap(value); - if (unwrapped == nullptr) { - return QVariant(); - } - - QVariant var; - if (!castValueToVariant(unwrapped->toQtValue(), var, typeId)) { - return QVariant(); - } - - int destType = var.userType(); - if (destType != typeId) { - var.convert(typeId); // if conversion fails then var is set to QVariant() - } - - return var; -} diff --git a/libraries/script-engine/src/qtscript/ScriptObjectQtProxy.cpp b/libraries/script-engine/src/qtscript/ScriptObjectQtProxy.cpp deleted file mode 100644 index a9a7e54e19..0000000000 --- a/libraries/script-engine/src/qtscript/ScriptObjectQtProxy.cpp +++ /dev/null @@ -1,794 +0,0 @@ -// -// ScriptObjectQtProxy.cpp -// libraries/script-engine/src/qtscript -// -// Created by Heather Anderson on 12/5/21. -// Copyright 2021 Vircadia contributors. -// Copyright 2022 Overte e.V. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "ScriptObjectQtProxy.h" - -#include -#include - -#include - -#include "../ScriptEngineLogging.h" - -#include "ScriptContextQtWrapper.h" -#include "ScriptValueQtWrapper.h" - -Q_DECLARE_METATYPE(QScriptContext*) -Q_DECLARE_METATYPE(ScriptValue) -Q_DECLARE_METATYPE(QScriptValue) - -Q_DECLARE_METATYPE(QSharedPointer) -Q_DECLARE_METATYPE(QSharedPointer) - -// Used strictly to replace the "this" object value for property access. May expand to a full context element -// if we find it necessary to, but hopefully not needed -class ScriptPropertyContextQtWrapper final : public ScriptContext { -public: // construction - inline ScriptPropertyContextQtWrapper(const ScriptValue& object, ScriptContext* parentContext) : - _parent(parentContext), _object(object) {} - -public: // ScriptContext implementation - virtual int argumentCount() const override { return _parent->argumentCount(); } - virtual ScriptValue argument(int index) const override { return _parent->argument(index); } - virtual QStringList backtrace() const override { return _parent->backtrace(); } - virtual ScriptValue callee() const override { return _parent->callee(); } - virtual ScriptEnginePointer engine() const override { return _parent->engine(); } - virtual ScriptFunctionContextPointer functionContext() const override { return _parent->functionContext(); } - virtual ScriptContextPointer parentContext() const override { return _parent->parentContext(); } - virtual ScriptValue thisObject() const override { return _object; } - virtual ScriptValue throwError(const QString& text) override { return _parent->throwError(text); } - virtual ScriptValue throwValue(const ScriptValue& value) override { return _parent->throwValue(value); } - -private: // storage - ScriptContext* _parent; - const ScriptValue& _object; -}; - -QScriptValue ScriptObjectQtProxy::newQObject(ScriptEngineQtScript* engine, QObject* object, - ScriptEngine::ValueOwnership ownership, - const ScriptEngine::QObjectWrapOptions& options) { - QScriptEngine* qengine = static_cast(engine); - - // do we already have a valid wrapper for this QObject? - { - QMutexLocker guard(&engine->_qobjectWrapperMapProtect); - ScriptEngineQtScript::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object); - if (lookup != engine->_qobjectWrapperMap.end()) { - QSharedPointer proxy = lookup.value().lock(); - if (proxy) return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));; - } - } - - bool ownsObject; - switch (ownership) { - case ScriptEngine::QtOwnership: - ownsObject = false; - break; - case ScriptEngine::ScriptOwnership: - ownsObject = true; - break; - case ScriptEngine::AutoOwnership: - ownsObject = !object->parent(); - break; - default: - ownsObject = false; - qCritical() << "Wrong ScriptEngine::ValueOwnership value: " << ownership; - break; - } - - // create the wrapper - auto proxy = QSharedPointer::create(engine, object, ownsObject, options); - - { - QMutexLocker guard(&engine->_qobjectWrapperMapProtect); - - // check again to see if someone else created the wrapper while we were busy - ScriptEngineQtScript::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object); - if (lookup != engine->_qobjectWrapperMap.end()) { - QSharedPointer proxy = lookup.value().lock(); - if (proxy) return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));; - } - - // register the wrapper with the engine and make sure it cleans itself up - engine->_qobjectWrapperMap.insert(object, proxy); - QPointer enginePtr = engine; - object->connect(object, &QObject::destroyed, engine, [enginePtr, object]() { - if (!enginePtr) return; - QMutexLocker guard(&enginePtr->_qobjectWrapperMapProtect); - ScriptEngineQtScript::ObjectWrapperMap::iterator lookup = enginePtr->_qobjectWrapperMap.find(object); - if (lookup != enginePtr->_qobjectWrapperMap.end()) { - enginePtr->_qobjectWrapperMap.erase(lookup); - } - }); - } - - return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy))); -} - -ScriptObjectQtProxy* ScriptObjectQtProxy::unwrapProxy(const QScriptValue& val) { - QScriptClass* scriptClass = val.scriptClass(); - return scriptClass ? dynamic_cast(scriptClass) : nullptr; -} - -QObject* ScriptObjectQtProxy::unwrap(const QScriptValue& val) { - if (val.isQObject()) { - return val.toQObject(); - } - ScriptObjectQtProxy* proxy = unwrapProxy(val); - return proxy ? proxy->toQtValue() : nullptr; -} - -ScriptObjectQtProxy::~ScriptObjectQtProxy() { - if (_ownsObject) { - QObject* qobject = _object; - if(qobject) qobject->deleteLater(); - } -} - -void ScriptObjectQtProxy::investigate() { - QObject* qobject = _object; - Q_ASSERT(qobject); - if (!qobject) return; - - const QMetaObject* metaObject = qobject->metaObject(); - - // discover properties - int startIdx = _wrapOptions & ScriptEngine::ExcludeSuperClassProperties ? metaObject->propertyOffset() : 0; - int num = metaObject->propertyCount(); - for (int idx = startIdx; idx < num; ++idx) { - QMetaProperty prop = metaObject->property(idx); - if (!prop.isScriptable()) continue; - - // always exclude child objects (at least until we decide otherwise) - int metaTypeId = prop.userType(); - if (metaTypeId != QMetaType::UnknownType) { - QMetaType metaType(metaTypeId); - if (metaType.flags() & QMetaType::PointerToQObject) { - continue; - } - } - - PropertyDef& propDef = _props.insert(idx, PropertyDef()).value(); - propDef.name = _engine->toStringHandle(QString::fromLatin1(prop.name())); - propDef.flags = QScriptValue::Undeletable | QScriptValue::PropertyGetter | QScriptValue::PropertySetter | - QScriptValue::QObjectMember; - if (prop.isConstant()) propDef.flags |= QScriptValue::ReadOnly; - } - - // discover methods - startIdx = (_wrapOptions & ScriptEngine::ExcludeSuperClassMethods) ? metaObject->methodOffset() : 0; - num = metaObject->methodCount(); - QHash methodNames; - for (int idx = startIdx; idx < num; ++idx) { - QMetaMethod method = metaObject->method(idx); - - // perhaps keep this comment? Calls (like AudioScriptingInterface::playSound) seem to expect non-public methods to be script-accessible - /* if (method.access() != QMetaMethod::Public) continue;*/ - - bool isSignal = false; - QByteArray szName = method.name(); - - switch (method.methodType()) { - case QMetaMethod::Constructor: - continue; - case QMetaMethod::Signal: - isSignal = true; - break; - case QMetaMethod::Slot: - if (_wrapOptions & ScriptEngine::ExcludeSlots) { - continue; - } - if (szName == "deleteLater") { - continue; - } - break; - default: - break; - } - - QScriptString name = _engine->toStringHandle(QString::fromLatin1(szName)); - auto nameLookup = methodNames.find(name); - if (isSignal) { - if (nameLookup == methodNames.end()) { - SignalDef& signalDef = _signals.insert(idx, SignalDef()).value(); - signalDef.name = name; - signalDef.signal = method; - methodNames.insert(name, idx); - } else { - int originalMethodId = nameLookup.value(); - SignalDefMap::iterator signalLookup = _signals.find(originalMethodId); - Q_ASSERT(signalLookup != _signals.end()); - SignalDef& signalDef = signalLookup.value(); - Q_ASSERT(signalDef.signal.parameterCount() != method.parameterCount()); - if (signalDef.signal.parameterCount() < method.parameterCount()) { - signalDef.signal = method; - } - } - } else { - int parameterCount = method.parameterCount(); - if(method.returnType() == QMetaType::UnknownType) { - qCritical(scriptengine) << "Method " << metaObject->className() << "::" << name << " has QMetaType::UnknownType return value"; - } - for (int i = 0; i < method.parameterCount(); i++) { - if (method.parameterType(i) == QMetaType::UnknownType) { - qCritical(scriptengine) << "Parameter " << i << "in method " << metaObject->className() << "::" << name << " is of type QMetaType::UnknownType"; - } - } - if (nameLookup == methodNames.end()) { - MethodDef& methodDef = _methods.insert(idx, MethodDef()).value(); - methodDef.name = name; - methodDef.numMaxParms = parameterCount; - methodDef.methods.append(method); - methodNames.insert(name, idx); - } else { - int originalMethodId = nameLookup.value(); - MethodDefMap::iterator methodLookup = _methods.find(originalMethodId); - Q_ASSERT(methodLookup != _methods.end()); - MethodDef& methodDef = methodLookup.value(); - if(methodDef.numMaxParms < parameterCount) methodDef.numMaxParms = parameterCount; - methodDef.methods.append(method); - } - } - } -} - -QString ScriptObjectQtProxy::name() const { - Q_ASSERT(_object); - if (!_object) return ""; - return _object ? _object->objectName() : ""; - QString objectName = _object->objectName(); - if (!objectName.isEmpty()) return objectName; - return _object->metaObject()->className(); -} - -QScriptClass::QueryFlags ScriptObjectQtProxy::queryProperty(const QScriptValue& object, const QScriptString& name, QueryFlags flags, uint* id) { - // check for properties - for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) { - const PropertyDef& propDef = trans.value(); - if (propDef.name != name) continue; - *id = trans.key() | PROPERTY_TYPE; - return flags & (HandlesReadAccess | HandlesWriteAccess); - } - - // check for methods - for (MethodDefMap::const_iterator trans = _methods.cbegin(); trans != _methods.cend(); ++trans) { - if (trans.value().name != name) continue; - *id = trans.key() | METHOD_TYPE; - return flags & (HandlesReadAccess | HandlesWriteAccess); - } - - // check for signals - for (SignalDefMap::const_iterator trans = _signals.cbegin(); trans != _signals.cend(); ++trans) { - if (trans.value().name != name) continue; - *id = trans.key() | SIGNAL_TYPE; - return flags & (HandlesReadAccess | HandlesWriteAccess); - } - - return QueryFlags(); -} - -QScriptValue::PropertyFlags ScriptObjectQtProxy::propertyFlags(const QScriptValue& object, const QScriptString& name, uint id) { - QObject* qobject = _object; - if (!qobject) { - return QScriptValue::PropertyFlags(); - } - - switch (id & TYPE_MASK) { - case PROPERTY_TYPE: { - PropertyDefMap::const_iterator lookup = _props.find(id & ~TYPE_MASK); - if (lookup == _props.cend()) return QScriptValue::PropertyFlags(); - const PropertyDef& propDef = lookup.value(); - return propDef.flags; - } - case METHOD_TYPE: { - MethodDefMap::const_iterator lookup = _methods.find(id & ~TYPE_MASK); - if (lookup == _methods.cend()) return QScriptValue::PropertyFlags(); - return QScriptValue::ReadOnly | QScriptValue::Undeletable | QScriptValue::QObjectMember; - } - case SIGNAL_TYPE: { - SignalDefMap::const_iterator lookup = _signals.find(id & ~TYPE_MASK); - if (lookup == _signals.cend()) return QScriptValue::PropertyFlags(); - return QScriptValue::ReadOnly | QScriptValue::Undeletable | QScriptValue::QObjectMember; - } - } - return QScriptValue::PropertyFlags(); -} - -QScriptValue ScriptObjectQtProxy::property(const QScriptValue& object, const QScriptString& name, uint id) { - QObject* qobject = _object; - if (!qobject) { - QScriptContext* currentContext = static_cast(_engine)->currentContext(); - currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); - return QScriptValue(); - } - - const QMetaObject* metaObject = qobject->metaObject(); - - switch (id & TYPE_MASK) { - case PROPERTY_TYPE: { - int propId = id & ~TYPE_MASK; - PropertyDefMap::const_iterator lookup = _props.find(propId); - if (lookup == _props.cend()) return QScriptValue(); - - QMetaProperty prop = metaObject->property(propId); - ScriptValue scriptThis = ScriptValue(new ScriptValueQtWrapper(_engine, object)); - ScriptPropertyContextQtWrapper ourContext(scriptThis, _engine->currentContext()); - ScriptContextGuard guard(&ourContext); - - QVariant varValue = prop.read(qobject); - return _engine->castVariantToValue(varValue); - } - case METHOD_TYPE: { - int methodId = id & ~TYPE_MASK; - MethodDefMap::const_iterator lookup = _methods.find(methodId); - if (lookup == _methods.cend()) return QScriptValue(); - const MethodDef& methodDef = lookup.value(); - for (auto iter = methodDef.methods.begin(); iter != methodDef.methods.end(); iter++ ) { - if((*iter).returnType() == QMetaType::UnknownType) { - qDebug(scriptengine) << "Method with QMetaType::UnknownType " << metaObject->className() << " " << (*iter).name(); - } - } - return static_cast(_engine)->newObject( - new ScriptMethodQtProxy(_engine, qobject, object, methodDef.methods, methodDef.numMaxParms)); - } - case SIGNAL_TYPE: { - int signalId = id & ~TYPE_MASK; - SignalDefMap::const_iterator defLookup = _signals.find(signalId); - if (defLookup == _signals.cend()) return QScriptValue(); - - InstanceMap::const_iterator instLookup = _signalInstances.find(signalId); - if (instLookup == _signalInstances.cend() || instLookup.value().isNull()) { - instLookup = _signalInstances.insert(signalId, - new ScriptSignalQtProxy(_engine, qobject, object, defLookup.value().signal)); - Q_ASSERT(instLookup != _signalInstances.cend()); - } - ScriptSignalQtProxy* proxy = instLookup.value(); - - QScriptEngine::QObjectWrapOptions options = QScriptEngine::ExcludeSuperClassContents | - QScriptEngine::ExcludeDeleteLater | - QScriptEngine::PreferExistingWrapperObject; - return static_cast(_engine)->newQObject(proxy, QScriptEngine::ScriptOwnership, options); - } - } - return QScriptValue(); -} - -void ScriptObjectQtProxy::setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) { - if (!(id & PROPERTY_TYPE)) return; - QObject* qobject = _object; - if (!qobject) { - QScriptContext* currentContext = static_cast(_engine)->currentContext(); - currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); - return; - } - - int propId = id & ~TYPE_MASK; - PropertyDefMap::const_iterator lookup = _props.find(propId); - if (lookup == _props.cend()) return; - const PropertyDef& propDef = lookup.value(); - if (propDef.flags & QScriptValue::ReadOnly) return; - - const QMetaObject* metaObject = qobject->metaObject(); - QMetaProperty prop = metaObject->property(propId); - - ScriptValue scriptThis = ScriptValue(new ScriptValueQtWrapper(_engine, object)); - ScriptPropertyContextQtWrapper ourContext(scriptThis, _engine->currentContext()); - ScriptContextGuard guard(&ourContext); - - int propTypeId = prop.userType(); - Q_ASSERT(propTypeId != QMetaType::UnknownType); - QVariant varValue; - if(!_engine->castValueToVariant(value, varValue, propTypeId)) { - QByteArray propTypeName = QMetaType(propTypeId).name(); - QByteArray valTypeName = _engine->valueType(value).toLatin1(); - QScriptContext* currentContext = static_cast(_engine)->currentContext(); - currentContext->throwError(QScriptContext::TypeError, QString("Cannot convert %1 to %2").arg(valTypeName, propTypeName)); - return; - } - prop.write(qobject, varValue); -} - -ScriptVariantQtProxy::ScriptVariantQtProxy(ScriptEngineQtScript* engine, const QVariant& variant, QScriptValue scriptProto, ScriptObjectQtProxy* proto) : - QScriptClass(engine), _engine(engine), _variant(variant), _scriptProto(scriptProto), _proto(proto) { - _name = QString::fromLatin1(variant.typeName()); -} - -QScriptValue ScriptVariantQtProxy::newVariant(ScriptEngineQtScript* engine, const QVariant& variant, QScriptValue proto) { - QScriptEngine* qengine = static_cast(engine); - ScriptObjectQtProxy* protoProxy = ScriptObjectQtProxy::unwrapProxy(proto); - if (!protoProxy) { - Q_ASSERT(protoProxy); - return qengine->newVariant(variant); - } - auto proxy = QSharedPointer::create(engine, variant, proto, protoProxy); - return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy))); -} - -ScriptVariantQtProxy* ScriptVariantQtProxy::unwrapProxy(const QScriptValue& val) { - QScriptClass* scriptClass = val.scriptClass(); - return scriptClass ? dynamic_cast(scriptClass) : nullptr; -} - -QVariant ScriptVariantQtProxy::unwrap(const QScriptValue& val) { - ScriptVariantQtProxy* proxy = unwrapProxy(val); - return proxy ? proxy->toQtValue() : QVariant(); -} - -QString ScriptMethodQtProxy::fullName() const { - Q_ASSERT(_object); - if (!_object) return ""; - Q_ASSERT(!_metas.isEmpty()); - const QMetaMethod& firstMethod = _metas.front(); - QString objectName = _object->objectName(); - if (!objectName.isEmpty()) { - return QString("%1.%2").arg(objectName, firstMethod.name()); - } - return QString("%1::%2").arg(_object->metaObject()->className(), firstMethod.name()); -} - -bool ScriptMethodQtProxy::supportsExtension(Extension extension) const { - switch (extension) { - case Callable: - return true; - default: - return false; - } -} - -QVariant ScriptMethodQtProxy::extension(Extension extension, const QVariant& argument) { - if (extension != Callable) return QVariant(); - QScriptContext* context = qvariant_cast(argument); - - QObject* qobject = _object; - if (!qobject) { - context->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); - return QVariant(); - } - - int scriptNumArgs = context->argumentCount(); - int numArgs = std::min(scriptNumArgs, _numMaxParms); - - const int scriptValueTypeId = qMetaTypeId(); - - int parameterConversionFailureId = 0; - int parameterConversionFailureCount = 0; - - int num_metas = _metas.size(); - QVector< QList > qScriptArgLists; - QVector< QVector > qGenArgsVectors; - QVector< QList > qVarArgLists; - qScriptArgLists.resize(num_metas); - qGenArgsVectors.resize(num_metas); - qVarArgLists.resize(num_metas); - bool isValidMetaSelected = false; - int bestMeta = 0; - int bestConversionPenaltyScore = 0; - - for (int i = 0; i < num_metas; i++) { - const QMetaMethod& meta = _metas[i]; - int methodNumArgs = meta.parameterCount(); - if (methodNumArgs != numArgs) { - continue; - } - - qGenArgsVectors[i].resize(10); - int conversionPenaltyScore = 0; - int conversionFailures = 0; - - for (int arg = 0; arg < numArgs; ++arg) { - int methodArgTypeId = meta.parameterType(arg); - Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); - QScriptValue argVal = context->argument(arg); - if (methodArgTypeId == scriptValueTypeId) { - qScriptArgLists[i].append(ScriptValue(new ScriptValueQtWrapper(_engine, argVal))); - qGenArgsVectors[i][arg] = Q_ARG(ScriptValue, qScriptArgLists[i].back()); - } else if (methodArgTypeId == QMetaType::QVariant) { - qVarArgLists[i].append(argVal.toVariant()); - qGenArgsVectors[i][arg] = Q_ARG(QVariant, qVarArgLists[i].back()); - } else { - QVariant varArgVal; - if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) { - conversionFailures++; - } else { - qVarArgLists[i].append(varArgVal); - const QVariant& converted = qVarArgLists[i].back(); - conversionPenaltyScore = _engine->computeCastPenalty(argVal, methodArgTypeId); - - // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant - // A const_cast is needed because calling data() would detach the QVariant. - qGenArgsVectors[i][arg] = - QGenericArgument(QMetaType::typeName(converted.userType()), const_cast(converted.constData())); - } - } - } - if (conversionFailures) { - if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) { - parameterConversionFailureCount = conversionFailures; - parameterConversionFailureId = meta.methodIndex(); - } - continue; - } - - if (!isValidMetaSelected) { - isValidMetaSelected = true; - bestMeta = i; - bestConversionPenaltyScore = conversionPenaltyScore; - } - if (isValidMetaSelected && bestConversionPenaltyScore > conversionPenaltyScore) { - bestMeta = i; - bestConversionPenaltyScore = conversionPenaltyScore; - } - } - - if (isValidMetaSelected) { - ScriptContextQtWrapper ourContext(_engine, context); - ScriptContextGuard guard(&ourContext); - const QMetaMethod& meta = _metas[bestMeta]; - int returnTypeId = meta.returnType(); - QVector &qGenArgs = qGenArgsVectors[bestMeta]; - - // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's - // nothing in there for return values so these need to be explicitly runtime-registered! - Q_ASSERT(returnTypeId != QMetaType::UnknownType); - if (returnTypeId == QMetaType::UnknownType) { - context->throwError(QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName())); - return QVariant(); - } else if (returnTypeId == QMetaType::Void) { - bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], - qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); - if (!success) { - context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName())); - } - return QVariant(); - } else if (returnTypeId == scriptValueTypeId) { - ScriptValue result; - bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0], - qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], - qGenArgs[7], qGenArgs[8], qGenArgs[9]); - if (!success) { - context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName())); - return QVariant(); - } - QScriptValue qResult = ScriptValueQtWrapper::fullUnwrap(_engine, result); - return QVariant::fromValue(qResult); - } else { - // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant - const char* typeName = meta.typeName(); - QVariant qRetVal(returnTypeId, static_cast(NULL)); - QGenericReturnArgument sRetVal(typeName, const_cast(qRetVal.constData())); - - bool success = - meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], - qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); - if (!success) { - context->throwError(QString("Unexpected: Native call of %1 failed").arg(fullName())); - return QVariant(); - } - QScriptValue qResult = _engine->castVariantToValue(qRetVal); - return QVariant::fromValue(qResult); - } - } - - // we failed to convert the call to C++, try to create a somewhat sane error message - if (parameterConversionFailureCount == 0) { - context->throwError(QString("Native call of %1 failed: unexpected parameter count").arg(fullName())); - return QVariant(); - } - - const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId); - int methodNumArgs = meta.parameterCount(); - Q_ASSERT(methodNumArgs == numArgs); - - for (int arg = 0; arg < numArgs; ++arg) { - int methodArgTypeId = meta.parameterType(arg); - Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); - QScriptValue argVal = context->argument(arg); - if (methodArgTypeId != scriptValueTypeId && methodArgTypeId != QMetaType::QVariant) { - QVariant varArgVal; - if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) { - QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); - QByteArray argTypeName = _engine->valueType(argVal).toLatin1(); - context->throwError(QScriptContext::TypeError, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") - .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName)); - return QVariant(); - } - } - } - - context->throwError(QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName())); - Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now? - return QVariant(); -} - -QString ScriptSignalQtProxy::fullName() const { - Q_ASSERT(_object); - if (!_object) return ""; - QString objectName = _object->objectName(); - if (!objectName.isEmpty()) { - return QString("%1.%2").arg(objectName, _meta.name()); - } - return QString("%1::%2").arg(_object->metaObject()->className(), _meta.name()); -} - -// Adapted from https://doc.qt.io/archives/qq/qq16-dynamicqobject.html, for connecting to a signal without a compile-time definition for it -int ScriptSignalQtProxy::qt_metacall(QMetaObject::Call call, int id, void** arguments) { - id = ScriptSignalQtProxyBase::qt_metacall(call, id, arguments); - if (id != 0 || call != QMetaObject::InvokeMetaMethod) { - return id; - } - - QScriptValueList args; - int numArgs = _meta.parameterCount(); - for (int arg = 0; arg < numArgs; ++arg) { - int methodArgTypeId = _meta.parameterType(arg); - Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); - QVariant argValue(methodArgTypeId, arguments[arg+1]); - args.append(_engine->castVariantToValue(argValue)); - } - - QList connections; - withReadLock([&]{ - connections = _connections; - }); - - for (ConnectionList::iterator iter = connections.begin(); iter != connections.end(); ++iter) { - Connection& conn = *iter; - conn.callback.call(conn.thisValue, args); - } - - return -1; -} - -int ScriptSignalQtProxy::discoverMetaCallIdx() { - const QMetaObject* ourMeta = metaObject(); - return ourMeta->methodCount(); -} - -ScriptSignalQtProxy::ConnectionList::iterator ScriptSignalQtProxy::findConnection(QScriptValue thisObject, QScriptValue callback) { - auto iterOut = resultWithReadLock([&]{ - ConnectionList::iterator iter; - for (iter = _connections.begin(); iter != _connections.end(); ++iter) { - Connection& conn = *iter; - if (conn.callback.strictlyEquals(callback) && conn.thisValue.strictlyEquals(thisObject)) { - break; - } - } - return iter; - }); - return iterOut; -} - - -void ScriptSignalQtProxy::connect(QScriptValue arg0, QScriptValue arg1) { - QObject* qobject = _object; - if (!qobject) { - QScriptContext* currentContext = static_cast(_engine)->currentContext(); - currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); - return; - } - - // untangle the arguments - QScriptValue callback; - QScriptValue callbackThis; - if (arg1.isFunction()) { - callbackThis = arg0; - callback = arg1; - } else { - callback = arg0; - } - if (!callback.isFunction()) { - QScriptContext* currentContext = static_cast(_engine)->currentContext(); - currentContext->throwError(QScriptContext::TypeError, "Function expected as argument to 'connect'"); - return; - } - - // are we already connected? - { - ConnectionList::iterator lookup = findConnection(callbackThis, callback); - if (lookup != _connections.end()) { - return; // already exists - } - } - - // add a reference to ourselves to the destination callback - QScriptValue destData = callback.data(); - Q_ASSERT(!destData.isValid() || destData.isArray()); - if (!destData.isArray()) { - destData = static_cast(_engine)->newArray(); - } - { - QScriptValueList args; - args << thisObject(); - destData.property("push").call(destData, args); - } - callback.setData(destData); - - // add this to our internal list of connections - Connection newConn; - newConn.callback = callback; - newConn.thisValue = callbackThis; - - withWriteLock([&]{ - _connections.append(newConn); - }); - - // inform Qt that we're connecting to this signal - if (!_isConnected) { - auto result = QMetaObject::connect(qobject, _meta.methodIndex(), this, _metaCallId); - Q_ASSERT(result); - _isConnected = true; - } -} - -void ScriptSignalQtProxy::disconnect(QScriptValue arg0, QScriptValue arg1) { - QObject* qobject = _object; - if (!qobject) { - QScriptContext* currentContext = static_cast(_engine)->currentContext(); - currentContext->throwError(QScriptContext::ReferenceError, "Referencing deleted native object"); - return; - } - - // untangle the arguments - QScriptValue callback; - QScriptValue callbackThis; - if (arg1.isFunction()) { - callbackThis = arg0; - callback = arg1; - } else { - callback = arg0; - } - if (!callback.isFunction()) { - QScriptContext* currentContext = static_cast(_engine)->currentContext(); - currentContext->throwError(QScriptContext::TypeError, "Function expected as argument to 'disconnect'"); - return; - } - - // locate this connection in our list of connections - { - ConnectionList::iterator lookup = findConnection(callbackThis, callback); - if (lookup == _connections.end()) { - return; // not here - } - - // remove it from our internal list of connections - withWriteLock([&]{ - _connections.erase(lookup); - }); - } - - // remove a reference to ourselves from the destination callback - QScriptValue destData = callback.data(); - Q_ASSERT(destData.isArray()); - if (destData.isArray()) { - QScriptValue qThis = thisObject(); - int len = destData.property("length").toInteger(); - bool foundIt = false; - for (int idx = 0; idx < len && !foundIt; ++idx) { - QScriptValue entry = destData.property(idx); - if (entry.strictlyEquals(qThis)) { - foundIt = true; - QScriptValueList args; - args << idx << 1; - destData.property("splice").call(destData, args); - } - } - Q_ASSERT(foundIt); - } - - // inform Qt that we're no longer connected to this signal - if (_connections.empty()) { - Q_ASSERT(_isConnected); - bool result = QMetaObject::disconnect(qobject, _meta.methodIndex(), this, _metaCallId); - Q_ASSERT(result); - _isConnected = false; - } -} diff --git a/libraries/script-engine/src/qtscript/ScriptObjectQtProxy.h b/libraries/script-engine/src/qtscript/ScriptObjectQtProxy.h deleted file mode 100644 index e102bdd97a..0000000000 --- a/libraries/script-engine/src/qtscript/ScriptObjectQtProxy.h +++ /dev/null @@ -1,213 +0,0 @@ -// -// ScriptObjectQtProxy.h -// libraries/script-engine/src/qtscript -// -// Created by Heather Anderson on 12/5/21. -// Copyright 2021 Vircadia contributors. -// Copyright 2022 Overte e.V. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -/// @addtogroup ScriptEngine -/// @{ - -#ifndef hifi_ScriptObjectQtProxy_h -#define hifi_ScriptObjectQtProxy_h - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "../ScriptEngine.h" -#include "ScriptEngineQtScript.h" - -#include - -class ScriptEngineQtScript; -class ScriptSignalQtProxy; - -/// [QtScript] (re-)implements the translation layer between ScriptValue and QObject. This object -/// will focus exclusively on property get/set until function calls appear to be a problem -class ScriptObjectQtProxy final : public QScriptClass { -private: // implementation - struct PropertyDef { - QScriptString name; - QScriptValue::PropertyFlags flags; - }; - struct MethodDef { - QScriptString name; - int numMaxParms; - QList methods; - }; - struct SignalDef { - QScriptString name; - QMetaMethod signal; - }; - using PropertyDefMap = QHash; - using MethodDefMap = QHash; - using SignalDefMap = QHash; - using InstanceMap = QHash >; - - static constexpr uint PROPERTY_TYPE = 0x1000; - static constexpr uint METHOD_TYPE = 0x2000; - static constexpr uint SIGNAL_TYPE = 0x3000; - static constexpr uint TYPE_MASK = 0xF000; - -public: // construction - inline ScriptObjectQtProxy(ScriptEngineQtScript* engine, QObject* object, bool ownsObject, const ScriptEngine::QObjectWrapOptions& options) : - QScriptClass(engine), _engine(engine), _wrapOptions(options), _ownsObject(ownsObject), _object(object) { - investigate(); - } - virtual ~ScriptObjectQtProxy(); - - static QScriptValue newQObject(ScriptEngineQtScript* engine, - QObject* object, - ScriptEngine::ValueOwnership ownership = ScriptEngine::QtOwnership, - const ScriptEngine::QObjectWrapOptions& options = ScriptEngine::QObjectWrapOptions()); - static ScriptObjectQtProxy* unwrapProxy(const QScriptValue& val); - static QObject* unwrap(const QScriptValue& val); - inline QObject* toQtValue() const { return _object; } - -public: // QScriptClass implementation - virtual QString name() const override; - - virtual QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; - virtual QScriptValue::PropertyFlags propertyFlags(const QScriptValue& object, const QScriptString& name, uint id) override; - virtual QueryFlags queryProperty(const QScriptValue& object, const QScriptString& name, QueryFlags flags, uint* id) override; - virtual void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; - -private: // implementation - void investigate(); - -private: // storage - ScriptEngineQtScript* _engine; - const ScriptEngine::QObjectWrapOptions _wrapOptions; - PropertyDefMap _props; - MethodDefMap _methods; - SignalDefMap _signals; - InstanceMap _signalInstances; - const bool _ownsObject; - QPointer _object; - - Q_DISABLE_COPY(ScriptObjectQtProxy) -}; - -/// [QtScript] (re-)implements the translation layer between ScriptValue and QVariant where a prototype is set. -/// This object depends on a ScriptObjectQtProxy to provide the prototype's behavior -class ScriptVariantQtProxy final : public QScriptClass { -public: // construction - ScriptVariantQtProxy(ScriptEngineQtScript* engine, const QVariant& variant, QScriptValue scriptProto, ScriptObjectQtProxy* proto); - - static QScriptValue newVariant(ScriptEngineQtScript* engine, const QVariant& variant, QScriptValue proto); - static ScriptVariantQtProxy* unwrapProxy(const QScriptValue& val); - static QVariant unwrap(const QScriptValue& val); - inline QVariant toQtValue() const { return _variant; } - -public: // QScriptClass implementation - virtual QString name() const override { return _name; } - - virtual QScriptValue prototype() const override { return _scriptProto; } - - virtual QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override { - return _proto->property(object, name, id); - } - virtual QScriptValue::PropertyFlags propertyFlags(const QScriptValue& object, const QScriptString& name, uint id) override { - return _proto->propertyFlags(object, name, id); - } - virtual QueryFlags queryProperty(const QScriptValue& object, const QScriptString& name, QueryFlags flags, uint* id) override { - return _proto->queryProperty(object, name, flags, id); - } - virtual void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override { - return _proto->setProperty(object, name, id, value); - } - -private: // storage - ScriptEngineQtScript* _engine; - QVariant _variant; - QScriptValue _scriptProto; - ScriptObjectQtProxy* _proto; - QString _name; - - Q_DISABLE_COPY(ScriptVariantQtProxy) -}; - -class ScriptMethodQtProxy final : public QScriptClass { -public: // construction - inline ScriptMethodQtProxy(ScriptEngineQtScript* engine, QObject* object, QScriptValue lifetime, - const QList& metas, int numMaxParms) : - QScriptClass(engine), - _numMaxParms(numMaxParms), _engine(engine), _object(object), _objectLifetime(lifetime), _metas(metas) {} - -public: // QScriptClass implementation - virtual QString name() const override { return fullName(); } - virtual bool supportsExtension(Extension extension) const override; - virtual QVariant extension(Extension extension, const QVariant& argument = QVariant()) override; - -private: - QString fullName() const; - -private: // storage - const int _numMaxParms; - ScriptEngineQtScript* _engine; - QPointer _object; - QScriptValue _objectLifetime; - const QList _metas; - - Q_DISABLE_COPY(ScriptMethodQtProxy) -}; - -// This abstract base class serves solely to declare the Q_INVOKABLE methods for ScriptSignalQtProxy -// as we're overriding qt_metacall later for the signal callback yet still want to support -// metacalls for the connect/disconnect API -class ScriptSignalQtProxyBase : public QObject, protected QScriptable { - Q_OBJECT -public: // API - Q_INVOKABLE virtual void connect(QScriptValue arg0, QScriptValue arg1 = QScriptValue()) = 0; - Q_INVOKABLE virtual void disconnect(QScriptValue arg0, QScriptValue arg1 = QScriptValue()) = 0; -}; - -class ScriptSignalQtProxy final : public ScriptSignalQtProxyBase, public ReadWriteLockable { -private: // storage - struct Connection { - QScriptValue thisValue; - QScriptValue callback; - }; - using ConnectionList = QList; - -public: // construction - inline ScriptSignalQtProxy(ScriptEngineQtScript* engine, QObject* object, QScriptValue lifetime, const QMetaMethod& meta) : - _engine(engine), _object(object), _objectLifetime(lifetime), _meta(meta), _metaCallId(discoverMetaCallIdx()) {} - -private: // implementation - virtual int qt_metacall(QMetaObject::Call call, int id, void** arguments) override; - int discoverMetaCallIdx(); - ConnectionList::iterator findConnection(QScriptValue thisObject, QScriptValue callback); - QString fullName() const; - -public: // API - virtual void connect(QScriptValue arg0, QScriptValue arg1 = QScriptValue()) override; - virtual void disconnect(QScriptValue arg0, QScriptValue arg1 = QScriptValue()) override; - -private: // storage - ScriptEngineQtScript* _engine; - QPointer _object; - QScriptValue _objectLifetime; - const QMetaMethod _meta; - const int _metaCallId; - ConnectionList _connections; - bool _isConnected{ false }; - - Q_DISABLE_COPY(ScriptSignalQtProxy) -}; - -#endif // hifi_ScriptObjectQtProxy_h - -/// @} diff --git a/libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.cpp b/libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.cpp deleted file mode 100644 index 3a5b2ae68e..0000000000 --- a/libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// -// ScriptProgramQtWrapper.cpp -// libraries/script-engine/src/qtscript -// -// Created by Heather Anderson on 8/24/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 "ScriptProgramQtWrapper.h" - -#include - -#include "ScriptEngineQtScript.h" -#include "ScriptValueQtWrapper.h" - -ScriptProgramQtWrapper* ScriptProgramQtWrapper::unwrap(ScriptProgramPointer val) { - if (!val) { - return nullptr; - } - - return dynamic_cast(val.get()); -} - -ScriptSyntaxCheckResultPointer ScriptProgramQtWrapper::checkSyntax() const { - QScriptSyntaxCheckResult result = _engine->checkSyntax(_value.sourceCode()); - return std::make_shared(std::move(result)); -} - -QString ScriptProgramQtWrapper::fileName() const { - return _value.fileName(); -} - -QString ScriptProgramQtWrapper::sourceCode() const { - return _value.sourceCode(); -} - - -int ScriptSyntaxCheckResultQtWrapper::errorColumnNumber() const { - return _value.errorColumnNumber(); -} - -int ScriptSyntaxCheckResultQtWrapper::errorLineNumber() const { - return _value.errorLineNumber(); -} - -QString ScriptSyntaxCheckResultQtWrapper::errorMessage() const { - return _value.errorMessage(); -} - -ScriptSyntaxCheckResult::State ScriptSyntaxCheckResultQtWrapper::state() const { - return static_cast(_value.state()); -} diff --git a/libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.h b/libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.h deleted file mode 100644 index 6849b71339..0000000000 --- a/libraries/script-engine/src/qtscript/ScriptProgramQtWrapper.h +++ /dev/null @@ -1,61 +0,0 @@ -// -// ScriptProgramQtWrapper.h -// libraries/script-engine/src/qtscript -// -// 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 -// - -/// @addtogroup ScriptEngine -/// @{ - -#ifndef hifi_ScriptProgramQtWrapper_h -#define hifi_ScriptProgramQtWrapper_h - -#include -#include - -#include "../ScriptProgram.h" -#include "ScriptEngineQtScript.h" - -/// [QtScript] Implements ScriptProgram for QtScript and translates calls for QScriptProgram -class ScriptProgramQtWrapper final : 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 override; - virtual QString fileName() const override; - virtual QString sourceCode() const override; - -private: // storage - QPointer _engine; - QScriptProgram _value; -}; - -class ScriptSyntaxCheckResultQtWrapper final : public ScriptSyntaxCheckResult { -public: // construction - inline ScriptSyntaxCheckResultQtWrapper(QScriptSyntaxCheckResult&& value) : - _value(std::move(value)) {} - -public: // ScriptSyntaxCheckResult implementation - virtual int errorColumnNumber() const override; - virtual int errorLineNumber() const override; - virtual QString errorMessage() const override; - virtual State state() const override; - -private: // storage - QScriptSyntaxCheckResult _value; -}; - -#endif // hifi_ScriptValueQtWrapper_h - -/// @} diff --git a/libraries/script-engine/src/qtscript/ScriptValueIteratorQtWrapper.cpp b/libraries/script-engine/src/qtscript/ScriptValueIteratorQtWrapper.cpp deleted file mode 100644 index ea3a4fe542..0000000000 --- a/libraries/script-engine/src/qtscript/ScriptValueIteratorQtWrapper.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// -// ScriptValueIteratorQtWrapper.cpp -// libraries/script-engine/src/qtscript -// -// Created by Heather Anderson on 8/29/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 "ScriptValueIteratorQtWrapper.h" - -ScriptValue::PropertyFlags ScriptValueIteratorQtWrapper::flags() const { - return (ScriptValue::PropertyFlags)(int)_value.flags(); -} - -bool ScriptValueIteratorQtWrapper::hasNext() const { - return _value.hasNext(); -} - -QString ScriptValueIteratorQtWrapper::name() const { - return _value.name(); -} - -void ScriptValueIteratorQtWrapper::next() { - _value.next(); -} - -ScriptValue ScriptValueIteratorQtWrapper::value() const { - QScriptValue result = _value.value(); - return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); -} diff --git a/libraries/script-engine/src/qtscript/ScriptValueIteratorQtWrapper.h b/libraries/script-engine/src/qtscript/ScriptValueIteratorQtWrapper.h deleted file mode 100644 index 00b618c6de..0000000000 --- a/libraries/script-engine/src/qtscript/ScriptValueIteratorQtWrapper.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// ScriptValueIteratorQtWrapper.h -// libraries/script-engine/src/qtscript -// -// Created by Heather Anderson on 8/29/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 -// - -/// @addtogroup ScriptEngine -/// @{ - -#ifndef hifi_ScriptValueIteratorQtWrapper_h -#define hifi_ScriptValueIteratorQtWrapper_h - -#include -#include - -#include "../ScriptValueIterator.h" -#include "ScriptEngineQtScript.h" -#include "ScriptValueQtWrapper.h" - -/// [QtScript] Implements ScriptValueIterator for QtScript and translates calls for QScriptValueIterator -class ScriptValueIteratorQtWrapper final : public ScriptValueIterator { -public: // construction - inline ScriptValueIteratorQtWrapper(ScriptEngineQtScript* engine, const ScriptValue& object) : - _engine(engine), _value(ScriptValueQtWrapper::fullUnwrap(engine, object)) {} - inline ScriptValueIteratorQtWrapper(ScriptEngineQtScript* engine, const QScriptValue& object) : - _engine(engine), _value(object) {} - -public: // ScriptValueIterator implementation - virtual ScriptValue::PropertyFlags flags() const override; - virtual bool hasNext() const override; - virtual QString name() const override; - virtual void next() override; - virtual ScriptValue value() const override; - -private: // storage - QPointer _engine; - QScriptValueIterator _value; -}; - -#endif // hifi_ScriptValueIteratorQtWrapper_h - -/// @} diff --git a/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.cpp b/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.cpp deleted file mode 100644 index cae3de30f8..0000000000 --- a/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.cpp +++ /dev/null @@ -1,234 +0,0 @@ -// -// ScriptValueQtWrapper.cpp -// libraries/script-engine/src/qtscript -// -// 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" - -#include "ScriptValueIteratorQtWrapper.h" - -void ScriptValueQtWrapper::release() { - delete this; -} - -ScriptValueProxy* ScriptValueQtWrapper::copy() const { - return new ScriptValueQtWrapper(_engine, _value); -} - -ScriptValueQtWrapper* ScriptValueQtWrapper::unwrap(const ScriptValue& val) { - return dynamic_cast(val.ptr()); -} - -QScriptValue ScriptValueQtWrapper::fullUnwrap(const ScriptValue& value) const { - ScriptValueQtWrapper* unwrapped = unwrap(value); - if (unwrapped) { - if (unwrapped->engine().get() != _engine) { - return static_cast(_engine)->toScriptValue(unwrapped->toVariant()); - } else { - return unwrapped->toQtValue(); - } - } - QVariant varValue = value.toVariant(); - return _engine->castVariantToValue(varValue); -} - -QScriptValue ScriptValueQtWrapper::fullUnwrap(ScriptEngineQtScript* engine, const ScriptValue& value) { - ScriptValueQtWrapper* unwrapped = unwrap(value); - if (unwrapped) { - if (unwrapped->engine().get() != engine) { - return static_cast(engine)->toScriptValue(unwrapped->toVariant()); - } else { - return unwrapped->toQtValue(); - } - } - QVariant varValue = value.toVariant(); - return engine->castVariantToValue(varValue); -} - -ScriptValue ScriptValueQtWrapper::call(const ScriptValue& thisObject, const ScriptValueList& args) { - QScriptValue qThis = fullUnwrap(thisObject); - QScriptValueList qArgs; - for (ScriptValueList::const_iterator iter = args.begin(); iter != args.end(); ++iter) { - qArgs.push_back(fullUnwrap(*iter)); - } - QScriptValue result = _value.call(qThis, qArgs); - return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); -} - -ScriptValue ScriptValueQtWrapper::call(const ScriptValue& thisObject, const ScriptValue& arguments) { - QScriptValue qThis = fullUnwrap(thisObject); - QScriptValue qArgs = fullUnwrap(arguments); - QScriptValue result = _value.call(qThis, qArgs); - return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); -} - -ScriptValue ScriptValueQtWrapper::construct(const ScriptValueList& args) { - QScriptValueList qArgs; - for (ScriptValueList::const_iterator iter = args.begin(); iter != args.end(); ++iter) { - qArgs.push_back(fullUnwrap(*iter)); - } - QScriptValue result = _value.construct(qArgs); - return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); -} - -ScriptValue ScriptValueQtWrapper::construct(const ScriptValue& arguments) { - QScriptValue unwrapped = fullUnwrap(arguments); - QScriptValue result = _value.construct(unwrapped); - return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); -} - -ScriptValue ScriptValueQtWrapper::data() const { - QScriptValue result = _value.data(); - return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); -} - -ScriptEnginePointer ScriptValueQtWrapper::engine() const { - if (!_engine) { - return ScriptEnginePointer(); - } - return _engine->shared_from_this(); -} - -ScriptValueIteratorPointer ScriptValueQtWrapper::newIterator() const { - return std::make_shared(_engine, _value); -} - -ScriptValue ScriptValueQtWrapper::property(const QString& name, const ScriptValue::ResolveFlags& mode) const { - QScriptValue result = _value.property(name, (QScriptValue::ResolveFlags)(int)mode); - return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); -} - -ScriptValue ScriptValueQtWrapper::property(quint32 arrayIndex, const ScriptValue::ResolveFlags& mode) const { - QScriptValue result = _value.property(arrayIndex, (QScriptValue::ResolveFlags)(int)mode); - return ScriptValue(new ScriptValueQtWrapper(_engine, std::move(result))); -} - -void ScriptValueQtWrapper::setData(const ScriptValue& value) { - QScriptValue unwrapped = fullUnwrap(value); - _value.setData(unwrapped); -} - -void ScriptValueQtWrapper::setProperty(const QString& name, const ScriptValue& value, const ScriptValue::PropertyFlags& flags) { - QScriptValue unwrapped = fullUnwrap(value); - _value.setProperty(name, unwrapped, (QScriptValue::PropertyFlags)(int)flags); -} - -void ScriptValueQtWrapper::setProperty(quint32 arrayIndex, const ScriptValue& value, const ScriptValue::PropertyFlags& flags) { - QScriptValue unwrapped = fullUnwrap(value); - _value.setProperty(arrayIndex, unwrapped, (QScriptValue::PropertyFlags)(int)flags); -} - -void ScriptValueQtWrapper::setPrototype(const ScriptValue& prototype) { - ScriptValueQtWrapper* unwrappedPrototype = unwrap(prototype); - if (unwrappedPrototype) { - _value.setPrototype(unwrappedPrototype->toQtValue()); - } -} - -bool ScriptValueQtWrapper::strictlyEquals(const ScriptValue& 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 { - QVariant dest; - if (_engine->castValueToVariant(_value, dest, QMetaType::UnknownType)) { - return dest; - } else { - Q_ASSERT(false); - return QVariant(); - } -} - -QObject* ScriptValueQtWrapper::toQObject() const { - QVariant dest; - if (_engine->castValueToVariant(_value, dest, QMetaType::QObjectStar)) { - return dest.value(); - } else { - Q_ASSERT(false); - return nullptr; - } -} - -bool ScriptValueQtWrapper::equals(const ScriptValue& other) const { - ScriptValueQtWrapper* unwrappedOther = unwrap(other); - return unwrappedOther ? _value.equals(unwrappedOther->toQtValue()) : false; -} - -bool ScriptValueQtWrapper::isArray() const { - return _value.isArray(); -} - -bool ScriptValueQtWrapper::isBool() const { - return _value.isBool(); -} - -bool ScriptValueQtWrapper::isError() const { - return _value.isError(); -} - -bool ScriptValueQtWrapper::isFunction() const { - return _value.isFunction(); -} - -bool ScriptValueQtWrapper::isNumber() const { - return _value.isNumber(); -} - -bool ScriptValueQtWrapper::isNull() const { - return _value.isNull(); -} - -bool ScriptValueQtWrapper::isObject() const { - return _value.isObject(); -} - -bool ScriptValueQtWrapper::isString() const { - return _value.isString(); -} - -bool ScriptValueQtWrapper::isUndefined() const { - return _value.isUndefined(); -} - -bool ScriptValueQtWrapper::isValid() const { - return _value.isValid(); -} - -bool ScriptValueQtWrapper::isVariant() const { - return _value.isVariant(); -} diff --git a/libraries/script-engine/src/qtscript/TypedArrays.h b/libraries/script-engine/src/qtscript/TypedArrays.h deleted file mode 100644 index 0595ba3c63..0000000000 --- a/libraries/script-engine/src/qtscript/TypedArrays.h +++ /dev/null @@ -1,154 +0,0 @@ -// -// TypedArrays.h -// -// -// Created by Clement on 7/9/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 -// - -/// @addtogroup ScriptEngine -/// @{ - -#ifndef hifi_TypedArrays_h -#define hifi_TypedArrays_h - -#include "ArrayBufferViewClass.h" - -static const QString BYTES_PER_ELEMENT_PROPERTY_NAME = "BYTES_PER_ELEMENT"; -static const QString LENGTH_PROPERTY_NAME = "length"; - -static const QString INT_8_ARRAY_CLASS_NAME = "Int8Array"; -static const QString UINT_8_ARRAY_CLASS_NAME = "Uint8Array"; -static const QString UINT_8_CLAMPED_ARRAY_CLASS_NAME = "Uint8ClampedArray"; -static const QString INT_16_ARRAY_CLASS_NAME = "Int16Array"; -static const QString UINT_16_ARRAY_CLASS_NAME = "Uint16Array"; -static const QString INT_32_ARRAY_CLASS_NAME = "Int32Array"; -static const QString UINT_32_ARRAY_CLASS_NAME = "Uint32Array"; -static const QString FLOAT_32_ARRAY_CLASS_NAME = "Float32Array"; -static const QString FLOAT_64_ARRAY_CLASS_NAME = "Float64Array"; - -/// [QtScript] Implements the TypedArray scripting class -class TypedArray : public ArrayBufferViewClass { - Q_OBJECT -public: - TypedArray(ScriptEngineQtScript* scriptEngine, QString name); - virtual QScriptValue newInstance(quint32 length); - virtual QScriptValue newInstance(QScriptValue array); - virtual QScriptValue newInstance(QScriptValue buffer, quint32 byteOffset, quint32 length); - - virtual QueryFlags queryProperty(const QScriptValue& object, - const QScriptString& name, - QueryFlags flags, uint* id) override; - virtual QScriptValue property(const QScriptValue& object, - const QScriptString& name, uint id) override; - virtual void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override = 0; - virtual QScriptValue::PropertyFlags propertyFlags(const QScriptValue& object, - const QScriptString& name, uint id) override; - - QString name() const override; - QScriptValue prototype() const override; - -protected: - static QScriptValue construct(QScriptContext* context, QScriptEngine* engine); - - void setBytesPerElement(quint32 bytesPerElement); - - QScriptValue _proto; - QScriptValue _ctor; - - QScriptString _name; - QScriptString _bytesPerElementName; - QScriptString _lengthName; - - quint32 _bytesPerElement; - - friend class TypedArrayPrototype; -}; - -class Int8ArrayClass : public TypedArray { - Q_OBJECT -public: - Int8ArrayClass(ScriptEngineQtScript* scriptEngine); - - QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; - void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; -}; - -class Uint8ArrayClass : public TypedArray { - Q_OBJECT -public: - Uint8ArrayClass(ScriptEngineQtScript* scriptEngine); - - QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; - void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; -}; - -class Uint8ClampedArrayClass : public TypedArray { - Q_OBJECT -public: - Uint8ClampedArrayClass(ScriptEngineQtScript* scriptEngine); - - QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; - void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; -}; - -class Int16ArrayClass : public TypedArray { - Q_OBJECT -public: - Int16ArrayClass(ScriptEngineQtScript* scriptEngine); - - QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; - void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; -}; - -class Uint16ArrayClass : public TypedArray { - Q_OBJECT -public: - Uint16ArrayClass(ScriptEngineQtScript* scriptEngine); - - QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; - void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; -}; - -class Int32ArrayClass : public TypedArray { - Q_OBJECT -public: - Int32ArrayClass(ScriptEngineQtScript* scriptEngine); - - QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; - void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; -}; - -class Uint32ArrayClass : public TypedArray { - Q_OBJECT -public: - Uint32ArrayClass(ScriptEngineQtScript* scriptEngine); - - QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; - void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; -}; - -class Float32ArrayClass : public TypedArray { - Q_OBJECT -public: - Float32ArrayClass(ScriptEngineQtScript* scriptEngine); - - QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; - void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; -}; - -class Float64ArrayClass : public TypedArray { - Q_OBJECT -public: - Float64ArrayClass(ScriptEngineQtScript* scriptEngine); - - QScriptValue property(const QScriptValue& object, const QScriptString& name, uint id) override; - void setProperty(QScriptValue& object, const QScriptString& name, uint id, const QScriptValue& value) override; -}; - -#endif // hifi_TypedArrays_h - -/// @} diff --git a/libraries/script-engine/src/qtscript/ArrayBufferClass.cpp b/libraries/script-engine/src/v8/ArrayBufferClass.cpp similarity index 72% rename from libraries/script-engine/src/qtscript/ArrayBufferClass.cpp rename to libraries/script-engine/src/v8/ArrayBufferClass.cpp index 48524d3853..4eed133c81 100644 --- a/libraries/script-engine/src/qtscript/ArrayBufferClass.cpp +++ b/libraries/script-engine/src/v8/ArrayBufferClass.cpp @@ -15,21 +15,22 @@ #include "ArrayBufferPrototype.h" #include "DataViewClass.h" -#include "ScriptEngineQtScript.h" +#include "ScriptEngineV8.h" #include "TypedArrays.h" -static const QString CLASS_NAME = "ArrayBuffer"; +// V8TODO +/*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(QByteArray*) -ArrayBufferClass::ArrayBufferClass(ScriptEngineQtScript* scriptEngine) : +ArrayBufferClass::ArrayBufferClass(ScriptEngineV8* scriptEngine) : QObject(scriptEngine), QScriptClass(scriptEngine) { qScriptRegisterMetaType(engine(), toScriptValue, fromScriptValue); - QScriptValue global = engine()->globalObject(); + V8ScriptValue global = engine()->globalObject(); // Save string handles for quick lookup _name = engine()->toStringHandle(CLASS_NAME.toLatin1()); @@ -63,15 +64,15 @@ QScriptClass(scriptEngine) { new Float64ArrayClass(scriptEngine); } -QScriptValue ArrayBufferClass::newInstance(qint32 size) { +V8ScriptValue ArrayBufferClass::newInstance(qint32 size) { const qint32 MAX_LENGTH = 100000000; if (size < 0) { engine()->evaluate("throw \"ArgumentError: negative length\""); - return QScriptValue(); + return V8ScriptValue(); } if (size > MAX_LENGTH) { engine()->evaluate("throw \"ArgumentError: absurd length\""); - return QScriptValue(); + return V8ScriptValue(); } // 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) @@ -79,29 +80,29 @@ QScriptValue ArrayBufferClass::newInstance(qint32 size) { #endif QScriptEngine* eng = engine(); QVariant variant = QVariant::fromValue(QByteArray(size, 0)); - QScriptValue data = eng->newVariant(variant); + V8ScriptValue data = eng->newVariant(variant); return engine()->newObject(this, data); } -QScriptValue ArrayBufferClass::newInstance(const QByteArray& ba) { +V8ScriptValue ArrayBufferClass::newInstance(const QByteArray& ba) { QScriptEngine* eng = engine(); - QScriptValue data = eng->newVariant(QVariant::fromValue(ba)); + V8ScriptValue data = eng->newVariant(QVariant::fromValue(ba)); return eng->newObject(this, data); } -QScriptValue ArrayBufferClass::construct(QScriptContext* context, QScriptEngine* engine) { +V8ScriptValue ArrayBufferClass::construct(V8ScriptContext* context, QScriptEngine* engine) { ArrayBufferClass* cls = qscriptvalue_cast(context->callee().data()); if (!cls) { // return if callee (function called) is not of type ArrayBuffer - return QScriptValue(); + return V8ScriptValue(); } - QScriptValue arg = context->argument(0); + V8ScriptValue arg = context->argument(0); if (!arg.isValid() || !arg.isNumber()) { - return QScriptValue(); + return V8ScriptValue(); } quint32 size = arg.toInt32(); - QScriptValue newObject = cls->newInstance(size); + V8ScriptValue newObject = cls->newInstance(size); if (context->isCalledAsConstructor()) { // if called with keyword new, replace this object. @@ -112,59 +113,59 @@ QScriptValue ArrayBufferClass::construct(QScriptContext* context, QScriptEngine* return newObject; } -QScriptClass::QueryFlags ArrayBufferClass::queryProperty(const QScriptValue& object, - const QScriptString& name, +ScriptObjectV8Proxy::QueryFlags ArrayBufferClass::queryProperty(const V8ScriptValue& object, + const V8ScriptString& 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 QScriptClass::QueryFlags(); // No access + return ScriptObjectV8Proxy::QueryFlags(); // No access } -QScriptValue ArrayBufferClass::property(const QScriptValue& object, - const QScriptString& name, uint id) { +V8ScriptValue ArrayBufferClass::property(const V8ScriptValue& object, + const V8ScriptString& name, uint id) { QByteArray* ba = qscriptvalue_cast(object.data()); if (ba && name == _byteLength) { return ba->length(); } - return QScriptValue(); + return V8ScriptValue(); } -QScriptValue::PropertyFlags ArrayBufferClass::propertyFlags(const QScriptValue& object, - const QScriptString& name, uint id) { - return QScriptValue::Undeletable; +V8ScriptValue::PropertyFlags ArrayBufferClass::propertyFlags(const V8ScriptValue& object, + const V8ScriptString& name, uint id) { + return V8ScriptValue::Undeletable; } QString ArrayBufferClass::name() const { return _name.toString(); } -QScriptValue ArrayBufferClass::prototype() const { +V8ScriptValue ArrayBufferClass::prototype() const { return _proto; } -QScriptValue ArrayBufferClass::toScriptValue(QScriptEngine* engine, const QByteArray& ba) { - QScriptValue ctor = engine->globalObject().property(CLASS_NAME); +V8ScriptValue ArrayBufferClass::toScriptValue(QScriptEngine* engine, const QByteArray& ba) { + V8ScriptValue 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 V8ScriptValue::NullValue; } return cls->newInstance(ba); } -void ArrayBufferClass::fromScriptValue(const QScriptValue& object, QByteArray& byteArray) { +void ArrayBufferClass::fromScriptValue(const V8ScriptValue& 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}); + auto typedArray = Uint8Array.construct(V8ScriptValueList{object}); if (QByteArray* buffer = qscriptvalue_cast(typedArray.property("buffer"))) { byteArray = *buffer; } @@ -175,4 +176,4 @@ void ArrayBufferClass::fromScriptValue(const QScriptValue& object, QByteArray& b } } } - +*/ diff --git a/libraries/script-engine/src/v8/ArrayBufferClass.h b/libraries/script-engine/src/v8/ArrayBufferClass.h new file mode 100644 index 0000000000..4aff1e17f6 --- /dev/null +++ b/libraries/script-engine/src/v8/ArrayBufferClass.h @@ -0,0 +1,66 @@ +// +// ArrayBufferClass.h +// +// +// 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 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ArrayBufferClass_h +#define hifi_ArrayBufferClass_h + +#include +#include "libplatform/libplatform.h" +#include "v8.h" +#include + +#include "V8Types.h" +// V8TODO +/* +class ScriptEngineV8; + +/// [V8] Implements the ArrayBuffer scripting class +class ArrayBufferClass : public QObject, public ScriptClass { + Q_OBJECT +public: + ArrayBufferClass(ScriptEngineV8* scriptEngine); + V8ScriptValue newInstance(qint32 size); + V8ScriptValue newInstance(const QByteArray& ba); + + QueryFlags queryProperty(const V8ScriptValue& object, + const V8ScriptString& name, + QueryFlags flags, uint* id) override; + V8ScriptValue property(const V8ScriptValue& object, + const V8ScriptString& name, uint id) override; + V8ScriptValue::PropertyFlags propertyFlags(const V8ScriptValue& object, + const V8ScriptString& name, uint id) override; + + QString name() const override; + V8ScriptValue prototype() const override; + + +private: + static V8ScriptValue construct(V8ScriptContext* context, QScriptEngine* engine); + + static V8ScriptValue toScriptValue(QScriptEngine* eng, const QByteArray& ba); + static void fromScriptValue(const V8ScriptValue& obj, QByteArray& ba); + + V8ScriptValue _proto; + V8ScriptValue _ctor; + + // JS Object attributes + V8ScriptString _name; + V8ScriptString _byteLength; + +}; + +*/ +#endif // hifi_ArrayBufferClass_h + +/// @} diff --git a/libraries/script-engine/src/qtscript/ArrayBufferPrototype.cpp b/libraries/script-engine/src/v8/ArrayBufferPrototype.cpp similarity index 96% rename from libraries/script-engine/src/qtscript/ArrayBufferPrototype.cpp rename to libraries/script-engine/src/v8/ArrayBufferPrototype.cpp index ad5a68ec79..a4004df040 100644 --- a/libraries/script-engine/src/qtscript/ArrayBufferPrototype.cpp +++ b/libraries/script-engine/src/v8/ArrayBufferPrototype.cpp @@ -15,12 +15,14 @@ #include #include -#include +#include "libplatform/libplatform.h" +#include "v8.h" static const int QCOMPRESS_HEADER_POSITION = 0; static const int QCOMPRESS_HEADER_SIZE = 4; -Q_DECLARE_METATYPE(QByteArray*) +// V8TODO +/*Q_DECLARE_METATYPE(QByteArray*) ArrayBufferPrototype::ArrayBufferPrototype(QObject* parent) : QObject(parent) { } @@ -86,4 +88,4 @@ QByteArray ArrayBufferPrototype::recodeImage(const QString& sourceFormat, const QByteArray* ArrayBufferPrototype::thisArrayBuffer() const { return qscriptvalue_cast(thisObject().data()); -} +}*/ diff --git a/libraries/script-engine/src/qtscript/ArrayBufferPrototype.h b/libraries/script-engine/src/v8/ArrayBufferPrototype.h similarity index 70% rename from libraries/script-engine/src/qtscript/ArrayBufferPrototype.h rename to libraries/script-engine/src/v8/ArrayBufferPrototype.h index 1fc5948f6b..3f966396a4 100644 --- a/libraries/script-engine/src/qtscript/ArrayBufferPrototype.h +++ b/libraries/script-engine/src/v8/ArrayBufferPrototype.h @@ -15,11 +15,13 @@ #ifndef hifi_ArrayBufferPrototype_h #define hifi_ArrayBufferPrototype_h -#include -#include +// V8TODO +/*#include -/// [QtScript] The javascript functions associated with an ArrayBuffer instance prototype -class ArrayBufferPrototype : public QObject, public QScriptable { +#include "../Scriptable.h" + +/// [V8] The javascript functions associated with an ArrayBuffer instance prototype +class ArrayBufferPrototype : public QObject, public Scriptable { Q_OBJECT public: ArrayBufferPrototype(QObject* parent = NULL); @@ -33,7 +35,7 @@ public slots: private: QByteArray* thisArrayBuffer() const; }; - +*/ #endif // hifi_ArrayBufferPrototype_h /// @} diff --git a/libraries/script-engine/src/qtscript/ArrayBufferViewClass.cpp b/libraries/script-engine/src/v8/ArrayBufferViewClass.cpp similarity index 74% rename from libraries/script-engine/src/qtscript/ArrayBufferViewClass.cpp rename to libraries/script-engine/src/v8/ArrayBufferViewClass.cpp index e175f753be..87ed7874ef 100644 --- a/libraries/script-engine/src/qtscript/ArrayBufferViewClass.cpp +++ b/libraries/script-engine/src/v8/ArrayBufferViewClass.cpp @@ -10,11 +10,12 @@ // #include "ArrayBufferViewClass.h" -#include "ScriptEngineQtScript.h" +#include "ScriptEngineV8.h" Q_DECLARE_METATYPE(QByteArray*) -ArrayBufferViewClass::ArrayBufferViewClass(ScriptEngineQtScript* scriptEngine) : +// V8TODO +/*ArrayBufferViewClass::ArrayBufferViewClass(ScriptEngineV8* scriptEngine) : QObject(scriptEngine), QScriptClass(scriptEngine), _scriptEngine(scriptEngine) @@ -25,8 +26,8 @@ ArrayBufferViewClass::ArrayBufferViewClass(ScriptEngineQtScript* scriptEngine) : _byteLengthName = engine()->toStringHandle(BYTE_LENGTH_PROPERTY_NAME.toLatin1()); } -QScriptClass::QueryFlags ArrayBufferViewClass::queryProperty(const QScriptValue& object, - const QScriptString& name, +QScriptClass::QueryFlags ArrayBufferViewClass::queryProperty(const V8ScriptValue& object, + const V8ScriptString& name, QueryFlags flags, uint* id) { if (name == _bufferName || name == _byteOffsetName || name == _byteLengthName) { return flags &= HandlesReadAccess; // Only keep read access flags @@ -34,8 +35,8 @@ QScriptClass::QueryFlags ArrayBufferViewClass::queryProperty(const QScriptValue& return QScriptClass::QueryFlags(); // No access } -QScriptValue ArrayBufferViewClass::property(const QScriptValue& object, - const QScriptString& name, uint id) { +V8ScriptValue ArrayBufferViewClass::property(const V8ScriptValue& object, + const V8ScriptString& name, uint id) { if (name == _bufferName) { return object.data().property(_bufferName); } @@ -45,10 +46,10 @@ QScriptValue ArrayBufferViewClass::property(const QScriptValue& object, if (name == _byteLengthName) { return object.data().property(_byteLengthName); } - return QScriptValue(); + return V8ScriptValue(); } -QScriptValue::PropertyFlags ArrayBufferViewClass::propertyFlags(const QScriptValue& object, - const QScriptString& name, uint id) { - return QScriptValue::Undeletable; -} +V8ScriptValue::PropertyFlags ArrayBufferViewClass::propertyFlags(const V8ScriptValue& object, + const V8ScriptString& name, uint id) { + return V8ScriptValue::Undeletable; +}*/ diff --git a/libraries/script-engine/src/v8/ArrayBufferViewClass.h b/libraries/script-engine/src/v8/ArrayBufferViewClass.h new file mode 100644 index 0000000000..12ee0ddc49 --- /dev/null +++ b/libraries/script-engine/src/v8/ArrayBufferViewClass.h @@ -0,0 +1,54 @@ +// +// ArrayBufferViewClass.h +// +// +// Created by Clement on 7/8/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 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ArrayBufferViewClass_h +#define hifi_ArrayBufferViewClass_h + +#include +#include "V8Types.h" + +// V8TODO +/*class ScriptEngineV8; + +static const QString BUFFER_PROPERTY_NAME = "buffer"; +static const QString BYTE_OFFSET_PROPERTY_NAME = "byteOffset"; +static const QString BYTE_LENGTH_PROPERTY_NAME = "byteLength"; + +/// [V8] The base class containing common code for ArrayBuffer views +class ArrayBufferViewClass : public QObject, public QScriptClass { + Q_OBJECT +public: + ArrayBufferViewClass(ScriptEngineV8* scriptEngine); + + ScriptEngineV8* getScriptEngine() { return _scriptEngine; } + + virtual QueryFlags queryProperty(const V8ScriptValue& object, + const V8ScriptString& name, + QueryFlags flags, uint* id) override; + virtual V8ScriptValue property(const V8ScriptValue& object, + const V8ScriptString& name, uint id) override; + virtual V8ScriptValue::PropertyFlags propertyFlags(const V8ScriptValue& object, + const V8ScriptString& name, uint id) override; +protected: + // JS Object attributes + V8ScriptString _bufferName; + V8ScriptString _byteOffsetName; + V8ScriptString _byteLengthName; + + ScriptEngineV8* _scriptEngine; +}; +*/ +#endif // hifi_ArrayBufferViewClass_h + +/// @} diff --git a/libraries/script-engine/src/qtscript/DataViewClass.cpp b/libraries/script-engine/src/v8/DataViewClass.cpp similarity index 72% rename from libraries/script-engine/src/qtscript/DataViewClass.cpp rename to libraries/script-engine/src/v8/DataViewClass.cpp index 7d466ea42c..328e59253a 100644 --- a/libraries/script-engine/src/qtscript/DataViewClass.cpp +++ b/libraries/script-engine/src/v8/DataViewClass.cpp @@ -11,14 +11,15 @@ #include "DataViewClass.h" -#include "DataViewPrototype.h" +// V8TODO +/*#include "DataViewPrototype.h" Q_DECLARE_METATYPE(QByteArray*) static const QString DATA_VIEW_NAME = "DataView"; -DataViewClass::DataViewClass(ScriptEngineQtScript* scriptEngine) : ArrayBufferViewClass(scriptEngine) { - QScriptValue global = engine()->globalObject(); +DataViewClass::DataViewClass(ScriptEngineV8* scriptEngine) : ArrayBufferViewClass(scriptEngine) { + V8ScriptValue global = engine()->globalObject(); // Save string handles for quick lookup _name = engine()->toStringHandle(DATA_VIEW_NAME.toLatin1()); @@ -37,8 +38,8 @@ DataViewClass::DataViewClass(ScriptEngineQtScript* scriptEngine) : ArrayBufferVi engine()->globalObject().setProperty(name(), _ctor); } -QScriptValue DataViewClass::newInstance(QScriptValue buffer, quint32 byteOffset, quint32 byteLentgh) { - QScriptValue data = engine()->newObject(); +V8ScriptValue DataViewClass::newInstance(V8ScriptValue buffer, quint32 byteOffset, quint32 byteLentgh) { + V8ScriptValue data = engine()->newObject(); data.setProperty(_bufferName, buffer); data.setProperty(_byteOffsetName, byteOffset); data.setProperty(_byteLengthName, byteLentgh); @@ -46,36 +47,36 @@ QScriptValue DataViewClass::newInstance(QScriptValue buffer, quint32 byteOffset, return engine()->newObject(this, data); } -QScriptValue DataViewClass::construct(QScriptContext* context, QScriptEngine* engine) { +V8ScriptValue DataViewClass::construct(V8ScriptContext* context, QScriptEngine* engine) { DataViewClass* cls = qscriptvalue_cast(context->callee().data()); if (!cls || context->argumentCount() < 1) { - return QScriptValue(); + return V8ScriptValue(); } - QScriptValue bufferArg = context->argument(0); - QScriptValue byteOffsetArg = (context->argumentCount() >= 2) ? context->argument(1) : QScriptValue(); - QScriptValue byteLengthArg = (context->argumentCount() >= 3) ? context->argument(2) : QScriptValue(); + V8ScriptValue bufferArg = context->argument(0); + V8ScriptValue byteOffsetArg = (context->argumentCount() >= 2) ? context->argument(1) : V8ScriptValue(); + V8ScriptValue byteLengthArg = (context->argumentCount() >= 3) ? context->argument(2) : V8ScriptValue(); QByteArray* arrayBuffer = (bufferArg.isValid()) ? qscriptvalue_cast(bufferArg.data()) :NULL; if (!arrayBuffer) { engine->evaluate("throw \"TypeError: 1st argument not a ArrayBuffer\""); - return QScriptValue(); + return V8ScriptValue(); } if (byteOffsetArg.isNumber() && (byteOffsetArg.toInt32() < 0 || byteOffsetArg.toInt32() > arrayBuffer->size())) { engine->evaluate("throw \"RangeError: byteOffset out of range\""); - return QScriptValue(); + return V8ScriptValue(); } if (byteLengthArg.isNumber() && (byteLengthArg.toInt32() < 0 || byteOffsetArg.toInt32() + byteLengthArg.toInt32() > arrayBuffer->size())) { engine->evaluate("throw \"RangeError: byteLength out of range\""); - return QScriptValue(); + return V8ScriptValue(); } quint32 byteOffset = (byteOffsetArg.isNumber()) ? byteOffsetArg.toInt32() : 0; quint32 byteLength = (byteLengthArg.isNumber()) ? byteLengthArg.toInt32() : arrayBuffer->size() - byteOffset; - QScriptValue newObject = cls->newInstance(bufferArg, byteOffset, byteLength); + V8ScriptValue newObject = cls->newInstance(bufferArg, byteOffset, byteLength); if (context->isCalledAsConstructor()) { context->setThisObject(newObject); @@ -89,6 +90,7 @@ QString DataViewClass::name() const { return _name.toString(); } -QScriptValue DataViewClass::prototype() const { +V8ScriptValue DataViewClass::prototype() const { return _proto; } +*/ diff --git a/libraries/script-engine/src/qtscript/DataViewClass.h b/libraries/script-engine/src/v8/DataViewClass.h similarity index 50% rename from libraries/script-engine/src/qtscript/DataViewClass.h rename to libraries/script-engine/src/v8/DataViewClass.h index 8b587bbd06..96d3db93e3 100644 --- a/libraries/script-engine/src/qtscript/DataViewClass.h +++ b/libraries/script-engine/src/v8/DataViewClass.h @@ -15,27 +15,29 @@ #ifndef hifi_DataViewClass_h #define hifi_DataViewClass_h +// V8TODO +/* #include "ArrayBufferViewClass.h" -/// [QtScript] Implements the DataView scripting class +/// [V8] Implements the DataView scripting class class DataViewClass : public ArrayBufferViewClass { Q_OBJECT public: - DataViewClass(ScriptEngineQtScript* scriptEngine); - QScriptValue newInstance(QScriptValue buffer, quint32 byteOffset, quint32 byteLength); + DataViewClass(ScriptEngineV8* scriptEngine); + V8ScriptValue newInstance(V8ScriptValue buffer, quint32 byteOffset, quint32 byteLength); QString name() const override; - QScriptValue prototype() const override; + V8ScriptValue prototype() const override; private: - static QScriptValue construct(QScriptContext* context, QScriptEngine* engine); + static V8ScriptValue construct(V8ScriptContext* context, QScriptEngine* engine); - QScriptValue _proto; - QScriptValue _ctor; + V8ScriptValue _proto; + V8ScriptValue _ctor; - QScriptString _name; + V8ScriptString _name; }; - +*/ #endif // hifi_DataViewClass_h diff --git a/libraries/script-engine/src/qtscript/DataViewPrototype.cpp b/libraries/script-engine/src/v8/DataViewPrototype.cpp similarity index 94% rename from libraries/script-engine/src/qtscript/DataViewPrototype.cpp rename to libraries/script-engine/src/v8/DataViewPrototype.cpp index 7770e3bce5..f2d63f7711 100644 --- a/libraries/script-engine/src/qtscript/DataViewPrototype.cpp +++ b/libraries/script-engine/src/v8/DataViewPrototype.cpp @@ -13,8 +13,8 @@ #include #include -#include -#include +#include "libplatform/libplatform.h" +#include "v8.h" #include @@ -22,13 +22,14 @@ #include "ArrayBufferViewClass.h" -Q_DECLARE_METATYPE(QByteArray*) +// V8TODO +/*Q_DECLARE_METATYPE(QByteArray*) DataViewPrototype::DataViewPrototype(QObject* parent) : QObject(parent) { } QByteArray* DataViewPrototype::thisArrayBuffer() const { - QScriptValue bufferObject = thisObject().data().property(BUFFER_PROPERTY_NAME); + V8ScriptValue bufferObject = thisObject().data().property(BUFFER_PROPERTY_NAME); return qscriptvalue_cast(bufferObject.data()); } @@ -124,7 +125,7 @@ quint32 DataViewPrototype::getUint32(qint32 byteOffset, bool littleEndian) { return 0; } -QScriptValue DataViewPrototype::getFloat32(qint32 byteOffset, bool littleEndian) { +V8ScriptValue DataViewPrototype::getFloat32(qint32 byteOffset, bool littleEndian) { if (realOffset(byteOffset, sizeof(float))) { QDataStream stream(*thisArrayBuffer()); stream.skipRawData(byteOffset); @@ -134,16 +135,16 @@ QScriptValue DataViewPrototype::getFloat32(qint32 byteOffset, bool littleEndian) float result; stream >> result; if (isNaN(result)) { - return QScriptValue(); + return V8ScriptValue(); } - return QScriptValue(result); + return V8ScriptValue(result); } thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); - return QScriptValue(); + return V8ScriptValue(); } -QScriptValue DataViewPrototype::getFloat64(qint32 byteOffset, bool littleEndian) { +V8ScriptValue DataViewPrototype::getFloat64(qint32 byteOffset, bool littleEndian) { if (realOffset(byteOffset, sizeof(double))) { QDataStream stream(*thisArrayBuffer()); stream.skipRawData(byteOffset); @@ -153,13 +154,13 @@ QScriptValue DataViewPrototype::getFloat64(qint32 byteOffset, bool littleEndian) double result; stream >> result; if (isNaN(result)) { - return QScriptValue(); + return V8ScriptValue(); } return result; } thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); - return QScriptValue(); + return V8ScriptValue(); } void DataViewPrototype::setInt8(qint32 byteOffset, qint32 value) { @@ -257,5 +258,5 @@ void DataViewPrototype::setFloat64(qint32 byteOffset, double value, bool littleE thisObject().engine()->evaluate("throw \"RangeError: byteOffset out of range\""); } } - +*/ diff --git a/libraries/script-engine/src/qtscript/DataViewPrototype.h b/libraries/script-engine/src/v8/DataViewPrototype.h similarity index 84% rename from libraries/script-engine/src/qtscript/DataViewPrototype.h rename to libraries/script-engine/src/v8/DataViewPrototype.h index 3f395516c1..21dce58188 100644 --- a/libraries/script-engine/src/qtscript/DataViewPrototype.h +++ b/libraries/script-engine/src/v8/DataViewPrototype.h @@ -16,10 +16,13 @@ #define hifi_DataViewPrototype_h #include -#include -/// [QtScript] The javascript functions associated with a DataView instance prototype -class DataViewPrototype : public QObject, public QScriptable { +#include "V8Types.h" +#include "../Scriptable.h" + +// V8TODO +/*/// [V8] The javascript functions associated with a DataView instance prototype +class DataViewPrototype : public QObject, public Scriptable { Q_OBJECT public: DataViewPrototype(QObject* parent = NULL); @@ -41,8 +44,8 @@ public slots: quint32 getUint16(qint32 byteOffset, bool littleEndian = false); qint32 getInt32(qint32 byteOffset, bool littleEndian = false); quint32 getUint32(qint32 byteOffset, bool littleEndian = false); - QScriptValue getFloat32(qint32 byteOffset, bool littleEndian = false); - QScriptValue getFloat64(qint32 byteOffset, bool littleEndian = false); + V8ScriptValue getFloat32(qint32 byteOffset, bool littleEndian = false); + V8ScriptValue getFloat64(qint32 byteOffset, bool littleEndian = false); // Stores a value of the given type at the specified byte offset // from the start of the view. There is no alignment constraint; @@ -68,7 +71,7 @@ private: QByteArray* thisArrayBuffer() const; bool realOffset(qint32& offset, size_t size) const; }; - +*/ #endif // hifi_DataViewPrototype_h /// @} diff --git a/libraries/script-engine/src/v8/ScriptContextV8Wrapper.cpp b/libraries/script-engine/src/v8/ScriptContextV8Wrapper.cpp new file mode 100644 index 0000000000..67ad87e715 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptContextV8Wrapper.cpp @@ -0,0 +1,135 @@ +// +// ScriptContextV8Wrapper.cpp +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 5/22/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptContextV8Wrapper.h" + +#include "ScriptEngineV8.h" +#include "ScriptValueV8Wrapper.h" + +ScriptContextV8Wrapper::ScriptContextV8Wrapper(ScriptEngineV8* engine, const v8::Local context) : _engine(engine) { + _context.Reset(_engine->getIsolate(), _engine->getConstContext()); +} + +ScriptContextV8Wrapper::ScriptContextV8Wrapper(ScriptEngineV8* engine, const v8::Local context, std::shared_ptr> functionCallbackInfo) : _context(engine->getIsolate(), engine->getConstContext()), _functionCallbackInfo(functionCallbackInfo), _engine(engine) { + _context.Reset(_engine->getIsolate(), _engine->getConstContext()); +} + +ScriptContextV8Wrapper* ScriptContextV8Wrapper::unwrap(ScriptContext* val) { + if (!val) { + return nullptr; + } + + return dynamic_cast(val); +} + +v8::Local ScriptContextV8Wrapper::toV8Value() const { + return _context.Get(_engine->getIsolate()); +} + +int ScriptContextV8Wrapper::argumentCount() const { + Q_ASSERT(_functionCallbackInfo); + return _functionCallbackInfo->Length(); +} + +ScriptValue ScriptContextV8Wrapper::argument(int index) const { + Q_ASSERT(_functionCallbackInfo); + v8::Local result = (*_functionCallbackInfo)[index]; + //V8ScriptValue result = _context->argument(index); + return ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine->getIsolate(), result))); +} + +QStringList ScriptContextV8Wrapper::backtrace() const { + auto isolate = _engine->getIsolate(); + v8::Local stackTrace = v8::StackTrace::CurrentStackTrace(isolate, 40); + QStringList backTrace; + //V8TODO nicer formatting + for (int i = 0; i < stackTrace->GetFrameCount(); i++) { + v8::Local stackFrame = stackTrace->GetFrame(isolate, i); + backTrace.append(QString(*v8::String::Utf8Value(isolate, stackFrame->GetScriptNameOrSourceURL())) + + QString(" ") + + QString(*v8::String::Utf8Value(isolate, stackFrame->GetFunctionName())) + + QString(":") + + QString(stackFrame->GetLineNumber()) + ); + } + return backTrace; +} + +ScriptValue ScriptContextV8Wrapper::callee() const { + //V8TODO + //Can this be done with CurrentStackTrace? + //V8ScriptValue result = _context->callee(); + //return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); + return ScriptValue(); +} + +ScriptEnginePointer ScriptContextV8Wrapper::engine() const { + return _engine->shared_from_this(); +} + +ScriptFunctionContextPointer ScriptContextV8Wrapper::functionContext() const { + return std::make_shared(_context.Get(_engine->getIsolate())); +} + +ScriptContextPointer ScriptContextV8Wrapper::parentContext() const { + //V8TODO + //V8ScriptContext* result = _context->parentContext(); + //return result ? std::make_shared(_engine, result) : ScriptContextPointer(); + return ScriptContextPointer(); +} + +ScriptValue ScriptContextV8Wrapper::thisObject() const { + Q_ASSERT(_functionCallbackInfo); + v8::Local result = _functionCallbackInfo->This(); + return ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine->getIsolate(), result))); + return ScriptValue(); +} + +ScriptValue ScriptContextV8Wrapper::throwError(const QString& text) { + V8ScriptValue result(_engine->getIsolate(), _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), text.toStdString().c_str()).ToLocalChecked())); + return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); +} + +ScriptValue ScriptContextV8Wrapper::throwValue(const ScriptValue& value) { + ScriptValueV8Wrapper* unwrapped = ScriptValueV8Wrapper::unwrap(value); + if (!unwrapped) { + return _engine->undefinedValue(); + } + V8ScriptValue result(_engine->getIsolate(), _engine->getIsolate()->ThrowException(unwrapped->toV8Value().constGet())); + return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); +} + + +QString ScriptFunctionContextV8Wrapper::fileName() const { + //V8TODO + //return _value.fileName(); + return QString(""); +} + +QString ScriptFunctionContextV8Wrapper::functionName() const { + //V8TODO + //return _value.functionName(); + return QString(""); +} + +ScriptFunctionContext::FunctionType ScriptFunctionContextV8Wrapper::functionType() const { + //V8TODO + //return static_cast(_value.functionType()); + return ScriptFunctionContext::FunctionType::ScriptFunction; +} + +int ScriptFunctionContextV8Wrapper::lineNumber() const { + //V8TODO + //return _value.lineNumber(); + return 0; +} diff --git a/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.h b/libraries/script-engine/src/v8/ScriptContextV8Wrapper.h similarity index 52% rename from libraries/script-engine/src/qtscript/ScriptContextQtWrapper.h rename to libraries/script-engine/src/v8/ScriptContextV8Wrapper.h index bbf9f0465c..2f001da10d 100644 --- a/libraries/script-engine/src/qtscript/ScriptContextQtWrapper.h +++ b/libraries/script-engine/src/v8/ScriptContextV8Wrapper.h @@ -1,9 +1,11 @@ // -// ScriptContextQtWrapper.h -// libraries/script-engine/src/qtscript +// ScriptContextV8Wrapper.h +// libraries/script-engine/src/v8 // // Created by Heather Anderson on 5/22/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 // Copyright 2021 Vircadia contributors. +// Copyright 2022 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -12,24 +14,27 @@ /// @addtogroup ScriptEngine /// @{ -#ifndef hifi_ScriptContextQtWrapper_h -#define hifi_ScriptContextQtWrapper_h +#ifndef hifi_ScriptContextV8Wrapper_h +#define hifi_ScriptContextV8Wrapper_h #include -#include #include "../ScriptContext.h" #include "../ScriptValue.h" -class QScriptContext; -class ScriptEngineQtScript; +#include +#include -/// [QtScript] Implements ScriptContext for QtScript and translates calls for QScriptContextInfo -class ScriptContextQtWrapper final : public ScriptContext { +//class V8ScriptContext; +class ScriptEngineV8; + +/// [V8] Implements ScriptContext for V8 and translates calls for V8ScriptContextInfo +class ScriptContextV8Wrapper final : public ScriptContext { public: // construction - inline ScriptContextQtWrapper(ScriptEngineQtScript* engine, QScriptContext* context) : _context(context) , _engine(engine) {} - static ScriptContextQtWrapper* unwrap(ScriptContext* val); - inline QScriptContext* toQtValue() const { return _context; } + ScriptContextV8Wrapper(ScriptEngineV8* engine, const v8::Local context); + ScriptContextV8Wrapper(ScriptEngineV8* engine, const v8::Local context, std::shared_ptr> functionCallbackInfo); + static ScriptContextV8Wrapper* unwrap(ScriptContext* val); + v8::Local toV8Value() const; public: // ScriptContext implementation virtual int argumentCount() const override; @@ -44,13 +49,14 @@ public: // ScriptContext implementation virtual ScriptValue throwValue(const ScriptValue& value) override; private: // storage - QScriptContext* _context; - ScriptEngineQtScript* _engine; + v8::Persistent _context; + std::shared_ptr> _functionCallbackInfo; + ScriptEngineV8* _engine; }; -class ScriptFunctionContextQtWrapper final : public ScriptFunctionContext { +class ScriptFunctionContextV8Wrapper final : public ScriptFunctionContext { public: // construction - inline ScriptFunctionContextQtWrapper(QScriptContext* context) : _value(context) {} + inline ScriptFunctionContextV8Wrapper(v8::Local context) { } public: // ScriptFunctionContext implementation virtual QString fileName() const override; @@ -58,10 +64,10 @@ public: // ScriptFunctionContext implementation virtual FunctionType functionType() const override; virtual int lineNumber() const override; -private: // storage - QScriptContextInfo _value; +//private: // storage + //V8ScriptContextInfo _value; }; -#endif // hifi_ScriptContextQtWrapper_h +#endif // hifi_ScriptContextV8Wrapper_h /// @} diff --git a/libraries/script-engine/src/v8/ScriptEngineV8.cpp b/libraries/script-engine/src/v8/ScriptEngineV8.cpp new file mode 100644 index 0000000000..a8d7b73c98 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptEngineV8.cpp @@ -0,0 +1,1093 @@ +// +// ScriptEngineV8.cpp +// libraries/script-engine/src/v8 +// +// Created by Brad Hefta-Gaub on 12/14/13. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2013 High Fidelity, Inc. +// Copyright 2022 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptEngineV8.h" + +#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 "ScriptContextV8Wrapper.h" +#include "ScriptObjectV8Proxy.h" +#include "ScriptProgramV8Wrapper.h" +#include "ScriptValueV8Wrapper.h" + +static const int MAX_DEBUG_VALUE_LENGTH { 80 }; + +std::once_flag ScriptEngineV8::_v8InitOnceFlag; +QMutex ScriptEngineV8::_v8InitMutex; + +bool ScriptEngineV8::IS_THREADSAFE_INVOCATION(const QThread* thread, const QString& method) { + const QThread* currentThread = QThread::currentThread(); + if (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 +V8ScriptValue ScriptEngineV8::makeError(const V8ScriptValue& _other, const QString& type) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return V8ScriptValue(_v8Isolate, v8::Null(_v8Isolate)); + } + return V8ScriptValue(_v8Isolate, v8::Null(_v8Isolate)); + //V8TODO + /* + auto other = _other; + if (_other.constGet()->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(V8ScriptValueList({ other.property("message") })); + + // transfer over any existing properties + V8ScriptValueIterator it(other); + while (it.hasNext()) { + it.next(); + err.setProperty(it.name(), it.value()); + } + return err;*/ +} + +ScriptValue ScriptEngineV8::makeError(const ScriptValue& _other, const QString& type) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + return nullValue(); + + //V8TODO + //what does makeError actually do? + /*ScriptValueV8Wrapper* unwrapped = ScriptValueV8Wrapper::unwrap(_other); + V8ScriptValue other; + if (_other.isString()) { + other = QScriptEngine::newObject(); + other.setProperty("message", _other.toString()); + } else if (unwrapped) { + other = unwrapped->toV8Value(); + } else { + other = QScriptEngine::newVariant(_other.toVariant()); + } + V8ScriptValue result = makeError(other, type); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result)));*/ +} + +// check syntax and when there are issues returns an actual "SyntaxError" with the details +ScriptValue ScriptEngineV8::cheskScriptSyntax(ScriptProgramPointer program) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + ScriptSyntaxCheckResultPointer syntaxCheck = program->checkSyntax(); + //V8TODO + if (syntaxCheck->state() != ScriptSyntaxCheckResult::Valid) { + auto err = globalObject().property("SyntaxError").construct(ScriptValueList({ newValue(syntaxCheck->errorMessage()) })); + err.setProperty("fileName", program->fileName()); + err.setProperty("lineNumber", syntaxCheck->errorLineNumber()); + err.setProperty("expressionBeginOffset", syntaxCheck->errorColumnNumber()); + err.setProperty("stack", syntaxCheck->errorBacktrace()); + //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, program->fileName(), line, column); + err.setProperty("formatted", message); + } + return err; + } + return undefinedValue(); +} +/*ScriptValue ScriptEngineV8::lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + //V8TODO + const auto syntaxCheck = checkSyntax(sourceCode); + if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { + auto err = QScriptEngine::globalObject().property("SyntaxError").construct(V8ScriptValueList({ 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 ScriptValue(new ScriptValueV8Wrapper(this, std::move(err))); + } + return undefinedValue(); +}*/ + +// this pulls from the best available information to create a detailed snapshot of the current exception +ScriptValue ScriptEngineV8::cloneUncaughtException(const QString& extraDetail) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + if (!hasUncaughtException()) { + return nullValue(); + } + return nullValue(); + //V8TODO + /* + 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()) { + V8ScriptContextInfo 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", fileNlintame); + 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 ScriptEngineV8::raiseException(const V8ScriptValue& exception) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return false; + } + //V8TODO + _v8Isolate->ThrowException(makeError(exception).get()); + /*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 (_scriptManager) { + // 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 + V8ScriptValue thrown = makeError(exception); + emit _scriptManager->unhandledException(ScriptValue(new ScriptValueV8Wrapper(this, std::move(thrown)))); + }*/ + //emit _scriptManager->unhandledException(ScriptValue(new ScriptValueV8Wrapper(this, std::move(thrown)))); + return false; +} + +bool ScriptEngineV8::maybeEmitUncaughtException(const QString& debugHint) { + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return false; + } + if (!isEvaluating() && hasUncaughtException() && _scriptManager) { + emit _scriptManager->unhandledException(cloneUncaughtException(debugHint)); + clearExceptions(); + return true; + } + return false; +} + +// Lambda +ScriptValue ScriptEngineV8::newLambdaFunction(std::function operation, + const V8ScriptValue& data, + const ValueOwnership& ownership) { + auto lambda = new Lambda(this, operation, data); + auto object = newQObject(lambda, ownership); + //V8TODO? + auto call = object.property("call"); + call.setPrototype(object); // context->callee().prototype() === Lambda QObject + call.setData(ScriptValue(new ScriptValueV8Wrapper(this, data))); // context->callee().data() will === data param + return call; +} +QString Lambda::toString() const { + v8::Local string; + QString qString(""); + if (_data.constGet()->ToString(_engine->getContext()).ToLocal(&string)) { + v8::String::Utf8Value utf8Value(_engine->getIsolate(), string); + qString = QString(*utf8Value); + } + //V8TODO it was data.isValid() originally + //I have no idea what happens here + return QString("[Lambda%1]").arg((!_data.constGet()->IsNullOrUndefined()) ? " " + qString : qString); +} + +Lambda::~Lambda() { +#ifdef DEBUG_JS_LAMBDA_FUNCS + qDebug() << "~Lambda" + << "this" << this; +#endif +} + +Lambda::Lambda(ScriptEngineV8* engine, + std::function operation, + V8ScriptValue data) : + _engine(engine), + _operation(operation), _data(data) { +#ifdef DEBUG_JS_LAMBDA_FUNCS + qDebug() << "Lambda" << data.toString(); +#endif +} +V8ScriptValue Lambda::call() { + if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { + return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); + } + return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); + // V8TODO: something is obviously wrong here, there's no call at all? + //return operation(static_cast(engine)->currentContext(), engine); +} + +#ifdef DEBUG_JS +void ScriptEngineV8::_debugDump(const QString& header, const V8ScriptValue& 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; + } + V8ScriptValueIterator it(object); + while (it.hasNext()) { + it.next(); + qCDebug(shared) << it.name() << ":" << it.value().toString(); + } + if (!footer.isEmpty()) { + qCDebug(shared) << footer; + } +} +#endif + +v8::Platform* ScriptEngineV8::getV8Platform() { + static std::unique_ptr platform = v8::platform::NewDefaultPlatform(); + return platform.get(); +} + +ScriptEngineV8::ScriptEngineV8(ScriptManager* scriptManager) : + _scriptManager(scriptManager) + //V8TODO + //_arrayBufferClass(new ArrayBufferClass(this)) +{ + _v8InitMutex.lock(); + std::call_once ( _v8InitOnceFlag, [ ]{ + v8::Platform* platform = getV8Platform(); + v8::V8::InitializePlatform(platform); + v8::V8::Initialize(); + } ); + _v8InitMutex.unlock(); + v8::Isolate::CreateParams isolateParams; + isolateParams.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); + _v8Isolate = v8::Isolate::New(isolateParams); + v8::Local context = v8::Context::New(_v8Isolate); + _v8Context = v8::UniquePersistent(_v8Isolate, context); + + registerSystemTypes(); + + _currentThread = QThread::currentThread(); + // V8TODO: port to V8 + /* + if (_scriptManager) { + connect(this, &QScriptEngine::signalHandlerException, this, [this](const V8ScriptValue& exception) { + if (hasUncaughtException()) { + // the engine's uncaughtException() seems to produce much better stack traces here + emit _scriptManager->unhandledException(cloneUncaughtException("signalHandlerException")); + clearExceptions(); + } else { + // ... but may not always be available -- so if needed we fallback to the passed exception + V8ScriptValue thrown = makeError(exception); + emit _scriptManager->unhandledException(ScriptValue(new ScriptValueV8Wrapper(this, std::move(thrown)))); + } + }, Qt::DirectConnection); + moveToThread(scriptManager->thread()); + }*/ + + // V8TODO: dispose of isolate on ScriptEngineV8 destruction + //v8::UniquePersistent null = v8::UniquePersistent(_v8Isolate, v8::Null(_v8Isolate)); + //_nullValue = ScriptValue(new ScriptValueV8Wrapper(this, std::move(null))); + V8ScriptValue nullScriptValue(_v8Isolate, v8::Null(_v8Isolate)); + _nullValue = ScriptValue(new ScriptValueV8Wrapper(this, nullScriptValue)); + + //V8ScriptValue undefined = v8::UniquePersistent(_v8Isolate,v8::Undefined(_v8Isolate)); + //_undefinedValue = ScriptValue(new ScriptValueV8Wrapper(this, std::move(undefined))); + V8ScriptValue undefined(_v8Isolate, v8::Undefined(_v8Isolate)); + _undefinedValue = ScriptValue(new ScriptValueV8Wrapper(this, undefined)); + + // V8TODO: + //QScriptEngine::setProcessEventsInterval(MSECS_PER_SECOND); +} + +void ScriptEngineV8::registerEnum(const QString& enumName, QMetaEnum newEnum) { + if (!newEnum.isValid()) { + qCCritical(scriptengine) << "registerEnum called on invalid enum with name " << enumName; + return; + } + + for (int i = 0; i < newEnum.keyCount(); i++) { + const char* keyName = newEnum.key(i); + QString fullName = enumName + "." + keyName; + registerValue(fullName, V8ScriptValue(_v8Isolate, v8::Integer::New(_v8Isolate, newEnum.keyToValue(keyName)))); + } +} + +void ScriptEngineV8::registerValue(const QString& valueName, V8ScriptValue value) { + //V8TODO + /*if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineV8::registerValue() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; +#endif + QMetaObject::invokeMethod(this, "registerValue", + Q_ARG(const QString&, valueName), + Q_ARG(V8ScriptValue, value)); + return; + }*/ + + QStringList pathToValue = valueName.split("."); + int partsToGo = pathToValue.length(); + v8::Local partObject = _v8Context.Get(_v8Isolate)->Global(); + + for (const auto& pathPart : pathToValue) { + partsToGo--; + v8::Local pathPartV8 = v8::String::NewFromUtf8(_v8Isolate, pathPart.toStdString().c_str(),v8::NewStringType::kNormal).ToLocalChecked(); + v8::Local currentPath; + if (!partObject->Get(_v8Context.Get(_v8Isolate), pathPartV8).ToLocal(¤tPath)) { + if (partsToGo > 0) { + //This was commented out + //QObject *object = new QObject; + v8::Local partValue = v8::Object::New(_v8Isolate); //newQObject(object, QScriptEngine::ScriptOwnership); + //V8ScriptValue partValue = QScriptEngine::newArray(); //newQObject(object, QScriptEngine::ScriptOwnership); + Q_ASSERT(partObject->Set(_v8Context.Get(_v8Isolate), pathPartV8, partValue).FromMaybe(false)); + } else { + //partObject = currentPath->ToObject(); + //V8TODO: do these still happen if asserts are disabled? + Q_ASSERT(partObject->Set(_v8Context.Get(_v8Isolate), pathPartV8, value.constGet()).FromMaybe(false)); + } + } + v8::Local child; + Q_ASSERT(partObject->Get(_v8Context.Get(_v8Isolate), pathPartV8).ToLocal(&child)); + } +} + +void ScriptEngineV8::registerGlobalObject(const QString& name, QObject* object) { + //V8TODO + /*if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineV8::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) << "ScriptEngineV8::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name; +#endif*/ + v8::Local globalObject = getContext()->Global(); + v8::Local v8Name = v8::String::NewFromUtf8(_v8Isolate, name.toStdString().c_str()).ToLocalChecked(); + + // V8TODO: Is IsEmpty check enough or IsValid is needed too? + if (!globalObject->Get(getContext(), v8Name).IsEmpty()) { + if (object) { + V8ScriptValue value = ScriptObjectV8Proxy::newQObject(this, object, ScriptEngine::QtOwnership); + globalObject->Set(getContext(), v8Name, value.get()); + } else { + globalObject->Set(getContext(), v8Name, v8::Null(_v8Isolate)); + } + } +} + +void ScriptEngineV8::registerFunction(const QString& name, ScriptEngine::FunctionSignature functionSignature, int numArguments) { + //V8TODO + /*if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineV8::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) << "ScriptEngineV8::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name; +#endif*/ + + //auto scriptFun = static_cast(newFunction(functionSignature, numArguments).ptr())->toV8Value().constGet(); + auto scriptFun = newFunction(functionSignature, numArguments); + + //getContext()->Global().Set(); + globalObject().setProperty(name, scriptFun); +} + +void ScriptEngineV8::registerFunction(const QString& parent, const QString& name, ScriptEngine::FunctionSignature functionSignature, int numArguments) { + //V8TODO + /*if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineV8::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) << "ScriptEngineV8::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name; +#endif*/ + + ScriptValue object = globalObject().property(parent); + if (object.isValid()) { + ScriptValue scriptFun = ScriptEngine::newFunction(functionSignature, numArguments); + object.setProperty(name, scriptFun); + } +} + +void ScriptEngineV8::registerGetterSetter(const QString& name, ScriptEngine::FunctionSignature getter, + ScriptEngine::FunctionSignature setter, const QString& parent) { + //V8TODO + /*if (QThread::currentThread() != QScriptEngine::thread()) { +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineV8::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(ScriptEngine::FunctionSignature, getter), + Q_ARG(ScriptEngine::FunctionSignature, setter), + Q_ARG(const QString&, parent)); + return; + } +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "ScriptEngineV8::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent; +#endif*/ + + ScriptValue setterFunction = newFunction(setter, 1); + ScriptValue getterFunction = newFunction(getter); + + if (!parent.isNull() && !parent.isEmpty()) { + ScriptValue object = ScriptEngine::globalObject().property(parent); + if (object.isValid()) { + object.setProperty(name, setterFunction, ScriptValue::PropertySetter); + object.setProperty(name, getterFunction, ScriptValue::PropertyGetter); + } + } else { + globalObject().setProperty(name, setterFunction, ScriptValue::PropertySetter); + globalObject().setProperty(name, getterFunction, ScriptValue::PropertyGetter); + } +} + +ScriptValue ScriptEngineV8::evaluateInClosure(const ScriptValue& _closure, + const ScriptProgramPointer& _program) { + PROFILE_RANGE(script, "evaluateInClosure"); + if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { + return nullValue(); + } + ScriptProgramV8Wrapper* unwrappedProgram = ScriptProgramV8Wrapper::unwrap(_program); + if (unwrappedProgram == nullptr) { + return nullValue(); + } + const V8ScriptProgram& program = unwrappedProgram->toV8Value(); + + const auto fileName = unwrappedProgram->fileName(); + const auto shortName = QUrl(fileName).fileName(); + + ScriptValueV8Wrapper* unwrappedClosure = ScriptValueV8Wrapper::unwrap(_closure); + if (unwrappedClosure == nullptr) { + return nullValue(); + } + const V8ScriptValue& closure = unwrappedClosure->toV8Value(); + if (!closure.constGet()->IsObject()) { + return nullValue(); + } + const v8::Local closureObject = v8::Local::Cast(closure.constGet()); + + v8::Local oldGlobal; + v8::Local closureGlobal; + if (!closureObject->Get(closure.constGetContext() ,v8::String::NewFromUtf8(_v8Isolate, "global").ToLocalChecked()).ToLocal(&closureGlobal)) { + return nullValue(); + } + + _v8Context.Get(_v8Isolate)->Exit(); + _v8Context.Get(_v8Isolate)->DetachGlobal(); + oldGlobal = _v8Context.Get(_v8Isolate)->Global(); + v8::Local closureContext; + + if (closureGlobal->IsObject()) { +#ifdef DEBUG_JS + qCDebug(shared) << " setting global = closure.global" << shortName; +#endif + closureContext = v8::Context::New(_v8Isolate, nullptr, v8::Local(), closureGlobal); + //setGlobalObject(global); + } else { + closureContext = v8::Context::New(_v8Isolate, nullptr, v8::Local(), oldGlobal); + } + + //auto context = pushContext(); + + v8::Local thiz; + // V8TODO: not sure if "this" doesn't exist or is empty in some cases + if (!closureObject->Get(closure.constGetContext() ,v8::String::NewFromUtf8(_v8Isolate, "this").ToLocalChecked()).ToLocal(&thiz)) { + return nullValue(); + } + //thiz = closure.property("this"); + if (thiz->IsObject()) { +#ifdef DEBUG_JS + qCDebug(shared) << " setting this = closure.this" << shortName; +#endif + //V8TODO I don't know how to do this in V8, will adding "this" to global object work? + closureContext->Global()->Set(closureContext, v8::String::NewFromUtf8(_v8Isolate, "this").ToLocalChecked(), thiz); + //context->setThisObject(thiz); + } + + //context->pushScope(closure); +#ifdef DEBUG_JS + qCDebug(shared) << QString("[%1] evaluateInClosure %2").arg(isEvaluating()).arg(shortName); +#endif + ScriptValue result; + { + v8::TryCatch tryCatch(getIsolate()); + auto maybeResult = program.constGet()->Run(closureContext); + v8::Local v8Result; + if (!maybeResult.ToLocal(&v8Result)) { + v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Exception()); + QString errorMessage = QString(*utf8Value); + qWarning() << __FUNCTION__ << "---------- hasCaught:" << errorMessage; + //V8TODO: better error reporting + } + + 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 = ScriptValue(new ScriptValueV8Wrapper(this, V8ScriptValue(_v8Isolate, v8Result))); + } + } +#ifdef DEBUG_JS + qCDebug(shared) << QString("[%1] //evaluateInClosure %2").arg(isEvaluating()).arg(shortName); +#endif + //popContext(); + closureContext->Exit(); + _v8Context.Get(_v8Isolate)->Enter(); + + //This is probably unnecessary in V8 + /*if (oldGlobal.isValid()) { +#ifdef DEBUG_JS + qCDebug(shared) << " restoring global" << shortName; +#endif + setGlobalObject(oldGlobal); + }*/ + + _v8Context.Get(_v8Isolate)->Enter(); + return result; +} + +ScriptValue ScriptEngineV8::evaluate(const QString& sourceCode, const QString& fileName) { + if (_scriptManager && _scriptManager->isStopped()) { + return undefinedValue(); // bail early + } + + //V8TODO + + /*if (QThread::currentThread() != QScriptEngine::thread()) { + ScriptValue result; +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineV8::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "sourceCode:" << sourceCode << " fileName:" << fileName; +#endif + BLOCKING_INVOKE_METHOD(this, "evaluate", + Q_RETURN_ARG(ScriptValue, result), + Q_ARG(const QString&, sourceCode), + Q_ARG(const QString&, fileName)); + return result; + }*/ + // Compile and check syntax + // V8TODO: Could these all be replaced with checkSyntax function from wrapper? + v8::TryCatch tryCatch(getIsolate()); + v8::ScriptOrigin scriptOrigin(getIsolate(), v8::String::NewFromUtf8(getIsolate(), fileName.toStdString().c_str()).ToLocalChecked()); + v8::Local script; + if (!v8::Script::Compile(getContext(), v8::String::NewFromUtf8(getIsolate(), sourceCode.toStdString().c_str()).ToLocalChecked(), &scriptOrigin).ToLocal(&script)) { + int errorColumnNumber = 0; + int errorLineNumber = 0; + QString errorMessage = ""; + QString errorBacktrace = ""; + v8::String::Utf8Value utf8Value(getIsolate(), tryCatch.Exception()); + errorMessage = QString(*utf8Value); + v8::Local exceptionMessage = tryCatch.Message(); + if (!exceptionMessage.IsEmpty()) { + errorLineNumber = exceptionMessage->GetLineNumber(getContext()).FromJust(); + errorColumnNumber = exceptionMessage->GetStartColumn(getContext()).FromJust(); + v8::Local backtraceV8String; + if (tryCatch.StackTrace(getContext()).ToLocal(&backtraceV8String) && backtraceV8String->IsString() && + v8::Local::Cast(backtraceV8String)->Length() > 0) { + v8::String::Utf8Value backtraceUtf8Value(getIsolate(), backtraceV8String); + errorBacktrace = *backtraceUtf8Value; + } + } + auto err = makeError(newValue(errorMessage)); + raiseException(err); + maybeEmitUncaughtException("compile"); + return err; + } + + //V8TODO + /*auto syntaxError = lintScript(sourceCode, fileName); + if (syntaxError.isError()) { + if (!isEvaluating()) { + syntaxError.setProperty("detail", "evaluate"); + } + raiseException(syntaxError); + maybeEmitUncaughtException("lint"); + return syntaxError; + }*/ + //V8TODO + /*if (script->IsNull()) { + // can this happen? + auto err = makeError(newValue("could not create V8ScriptProgram for " + fileName)); + raiseException(err); + maybeEmitUncaughtException("compile"); + return err; + }*/ + + v8::Local result; + v8::TryCatch tryCatchRun(getIsolate()); + if (!script->Run(getContext()).ToLocal(&result)) { + Q_ASSERT(tryCatchRun.HasCaught()); + auto runError = tryCatchRun.Message(); + ScriptValue errorValue(new ScriptValueV8Wrapper(this, V8ScriptValue(_v8Isolate, runError->Get()))); + raiseException(errorValue); + maybeEmitUncaughtException("evaluate"); + return errorValue; + } + V8ScriptValue resultValue(_v8Isolate, result); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(resultValue))); +} + +Q_INVOKABLE ScriptValue ScriptEngineV8::evaluate(const ScriptProgramPointer& program) { + if (_scriptManager && _scriptManager->isStopped()) { + return undefinedValue(); // bail early + } + + //V8TODO + /*if (QThread::currentThread() != QScriptEngine::thread()) { + ScriptValue result; +#ifdef THREAD_DEBUGGING + qCDebug(scriptengine) << "*** WARNING *** ScriptEngineV8::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " + "sourceCode:" << sourceCode << " fileName:" << fileName; +#endif + BLOCKING_INVOKE_METHOD(this, "evaluate", + Q_RETURN_ARG(ScriptValue, result), + Q_ARG(const ScriptProgramPointer&, program)); + return result; + }*/ + + ScriptProgramV8Wrapper* unwrapped = ScriptProgramV8Wrapper::unwrap(program); + if (!unwrapped) { + auto err = makeError(newValue("could not unwrap program")); + raiseException(err); + maybeEmitUncaughtException("compile"); + return err; + } + ScriptSyntaxCheckResultPointer syntaxCheck = unwrapped->checkSyntax(); + if (syntaxCheck->state() == ScriptSyntaxCheckResult::Error) { + auto err = makeError(newValue(syntaxCheck->errorMessage())); + raiseException(err); + maybeEmitUncaughtException("compile"); + return err; + } + + const V8ScriptProgram& v8Program = unwrapped->toV8Value(); + // V8TODO + /*if (qProgram.isNull()) { + // can this happen? + auto err = makeError(newValue("requested program is empty")); + raiseException(err); + maybeEmitUncaughtException("compile"); + return err; + }*/ + + v8::Local result; + v8::TryCatch tryCatchRun(getIsolate()); + if (!v8Program.constGet()->Run(getContext()).ToLocal(&result)) { + Q_ASSERT(tryCatchRun.HasCaught()); + auto runError = tryCatchRun.Message(); + ScriptValue errorValue(new ScriptValueV8Wrapper(this, V8ScriptValue(_v8Isolate, runError->Get()))); + raiseException(errorValue); + maybeEmitUncaughtException("evaluate"); + return errorValue; + } + V8ScriptValue resultValue(_v8Isolate, result); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(resultValue))); +} + + +void ScriptEngineV8::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 + } +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ScriptEngine implementation + +ScriptValue ScriptEngineV8::globalObject() const { + V8ScriptValue global(_v8Isolate, getConstContext()->Global());// = QScriptEngine::globalObject(); // can't cache the value as it may change + return ScriptValue(new ScriptValueV8Wrapper(const_cast(this), std::move(global))); +} + +ScriptManager* ScriptEngineV8::manager() const { + return _scriptManager; +} + +ScriptValue ScriptEngineV8::newArray(uint length) { + V8ScriptValue result(_v8Isolate, v8::Array::New(_v8Isolate, static_cast(length))); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newArrayBuffer(const QByteArray& message) { + //V8TODO: this will leak memory + std::shared_ptr backingStore(v8::ArrayBuffer::NewBackingStore(_v8Isolate, message.size())); + std::memcpy(backingStore.get()->Data(), message.constData(), message.size()); + auto arrayBuffer = v8::ArrayBuffer::New(_v8Isolate, backingStore); + /*V8ScriptValue data = QScriptEngine::newVariant(QVariant::fromValue(message)); + V8ScriptValue ctor = QScriptEngine::globalObject().property("ArrayBuffer"); + auto array = qscriptvalue_cast(ctor.data()); + if (!array) { + return undefinedValue(); + }*/ + V8ScriptValue result(_v8Isolate, arrayBuffer);//QScriptEngine::newObject(array, data); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newObject() { + V8ScriptValue result(_v8Isolate, v8::Object::New(_v8Isolate)); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newMethod(QObject* object, V8ScriptValue lifetime, + const QList& metas, int numMaxParams) { + V8ScriptValue result(ScriptMethodV8Proxy::newMethod(this, object, lifetime, metas, numMaxParams)); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptProgramPointer ScriptEngineV8::newProgram(const QString& sourceCode, const QString& fileName) { + //V8TODO: is it used between isolates? + //V8TODO: should it be compiled on creation? + //V8ScriptProgram result(sourceCode, fileName); + return std::make_shared(this, sourceCode, fileName); +} + +ScriptValue ScriptEngineV8::newQObject(QObject* object, + ScriptEngine::ValueOwnership ownership, + const ScriptEngine::QObjectWrapOptions& options) { + V8ScriptValue result = ScriptObjectV8Proxy::newQObject(this, object, ownership, options); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newValue(bool value) { + V8ScriptValue result(_v8Isolate, v8::Boolean::New(_v8Isolate, value)); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newValue(int value) { + V8ScriptValue result(_v8Isolate, v8::Integer::New(_v8Isolate, value)); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newValue(uint value) { + V8ScriptValue result(_v8Isolate, v8::Uint32::New(_v8Isolate, value)); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newValue(double value) { + V8ScriptValue result(_v8Isolate, v8::Number::New(_v8Isolate, value)); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newValue(const QString& value) { + v8::Local valueV8 = v8::String::NewFromUtf8(_v8Isolate, value.toStdString().c_str(), v8::NewStringType::kNormal).ToLocalChecked(); + V8ScriptValue result(_v8Isolate, valueV8); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newValue(const QLatin1String& value) { + v8::Local valueV8 = v8::String::NewFromUtf8(_v8Isolate, value.latin1(), v8::NewStringType::kNormal).ToLocalChecked(); + V8ScriptValue result(_v8Isolate, valueV8); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newValue(const char* value) { + v8::Local valueV8 = v8::String::NewFromUtf8(_v8Isolate, value, v8::NewStringType::kNormal).ToLocalChecked(); + V8ScriptValue result(_v8Isolate, valueV8); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::newVariant(const QVariant& value) { + V8ScriptValue result = castVariantToValue(value); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +ScriptValue ScriptEngineV8::nullValue() { + return _nullValue; +} + +ScriptValue ScriptEngineV8::undefinedValue() { + return _undefinedValue; +} + +void ScriptEngineV8::abortEvaluation() { + //V8TODO + //QScriptEngine::abortEvaluation(); +} + +void ScriptEngineV8::clearExceptions() { + //V8TODO + //QScriptEngine::clearExceptions(); +} + +ScriptContext* ScriptEngineV8::currentContext() const { + //V8TODO + /*V8ScriptContext* localCtx = QScriptEngine::currentContext(); + if (!localCtx) { + return nullptr; + } + if (!_currContext || _currContext->toV8Value() != localCtx) { + _currContext = std::make_shared(const_cast(this), localCtx); + }*/ + //_currContext = std::make_shared(const_cast(this), localCtx); + if (!_currContext) { + // I'm not sure how to do this without discardin const + _currContext = std::make_shared(const_cast(this), getConstContext()); + } + return _currContext.get(); +} + +bool ScriptEngineV8::hasUncaughtException() const { + //V8TODO + //return QScriptEngine::hasUncaughtException(); +} + +bool ScriptEngineV8::isEvaluating() const { + //V8TODO + //return QScriptEngine::isEvaluating(); +} + +ScriptValue ScriptEngineV8::newFunction(ScriptEngine::FunctionSignature fun, int length) { + /*auto innerFunc = [](V8ScriptContext* _context, QScriptEngine* _engine) -> V8ScriptValue { + auto callee = _context->callee(); + QVariant funAddr = callee.property("_func").toVariant(); + ScriptEngine::FunctionSignature fun = reinterpret_cast(funAddr.toULongLong()); + ScriptEngineV8* engine = static_cast(_engine); + ScriptContextV8Wrapper context(engine, _context); + ScriptValue result = fun(&context, engine); + ScriptValueV8Wrapper* unwrapped = ScriptValueV8Wrapper::unwrap(result); + return unwrapped ? unwrapped->toV8Value() : V8ScriptValue(); + };*/ + + auto v8FunctionCallback = [](const v8::FunctionCallbackInfo& info) { + //V8TODO: is using GetCurrentContext ok, or context wrapper needs to be added? + auto context = info.GetIsolate()->GetCurrentContext(); + auto function = reinterpret_cast + (info.Data()->ToObject(context).ToLocalChecked()->GetAlignedPointerFromInternalField(0)); + ScriptContext *scriptContext = reinterpret_cast + (info.Data()->ToObject(context).ToLocalChecked()->GetAlignedPointerFromInternalField(1));; + ScriptEngineV8 *scriptEngine = reinterpret_cast + (info.Data()->ToObject(context).ToLocalChecked()->GetAlignedPointerFromInternalField(2));; + ScriptValue result = function(scriptContext, scriptEngine); + ScriptValueV8Wrapper* unwrapped = ScriptValueV8Wrapper::unwrap(result); + info.GetReturnValue().Set(unwrapped->toV8Value().constGet()); + }; + //auto functionTemplate = v8::FunctionTemplate::New(_v8Isolate, v8FunctionCallback, v8::Local(), v8::Local(), length); + //auto functionData = v8::Object::New(_v8Isolate); + //functionData->setIn + auto functionDataTemplate = v8::ObjectTemplate::New(_v8Isolate); + functionDataTemplate->SetInternalFieldCount(3); + auto functionData = functionDataTemplate->NewInstance(getContext()).ToLocalChecked(); + functionData->SetAlignedPointerInInternalField(0, reinterpret_cast(fun)); + functionData->SetAlignedPointerInInternalField(1, reinterpret_cast(this)); + functionData->SetAlignedPointerInInternalField(2, reinterpret_cast(currentContext())); + //functionData->SetInternalField(3, v8::Null(_v8Isolate)); + auto v8Function = v8::Function::New(getContext(), v8FunctionCallback, functionData, length).ToLocalChecked(); + //auto functionObjectTemplate = functionTemplate->InstanceTemplate(); + //auto function = + V8ScriptValue result(_v8Isolate, v8Function); // = QScriptEngine::newFunction(innerFunc, length); + //auto funAddr = QScriptEngine::newVariant(QVariant(reinterpret_cast(fun))); + // V8TODO + //result.setProperty("_func", funAddr, V8ScriptValue::PropertyFlags(V8ScriptValue::ReadOnly + V8ScriptValue::Undeletable + V8ScriptValue::SkipInEnumeration)); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(result))); +} + +//V8TODO +/*void ScriptEngineV8::setObjectName(const QString& name) { + QScriptEngine::setObjectName(name); +}*/ + +//V8TODO +bool ScriptEngineV8::setProperty(const char* name, const QVariant& value) { + v8::Local global = getContext()->Global(); + auto v8Name = v8::String::NewFromUtf8(getIsolate(), name).ToLocalChecked(); + V8ScriptValue v8Value = castVariantToValue(value); + return global->Set(getContext(), v8Name, v8Value.get()).FromMaybe(false); +} + +void ScriptEngineV8::setProcessEventsInterval(int interval) { + //V8TODO + //QScriptEngine::setProcessEventsInterval(interval); +} + +QThread* ScriptEngineV8::thread() const { + return _currentThread; +} + +void ScriptEngineV8::setThread(QThread* thread) { + moveToThread(thread); +} + +ScriptValue ScriptEngineV8::uncaughtException() const { + //V8TODO + //V8ScriptValue result = QScriptEngine::uncaughtException(); + //return ScriptValue(new ScriptValueV8Wrapper(const_cast(this), std::move(result))); +} + +QStringList ScriptEngineV8::uncaughtExceptionBacktrace() const { + //V8TODO + //return QScriptEngine::uncaughtExceptionBacktrace(); +} + +int ScriptEngineV8::uncaughtExceptionLineNumber() const { + //V8TODO + //return QScriptEngine::uncaughtExceptionLineNumber(); +} + +bool ScriptEngineV8::raiseException(const ScriptValue& exception) { + //V8TODO + Q_ASSERT(false); + /*ScriptValueV8Wrapper* unwrapped = ScriptValueV8Wrapper::unwrap(exception); + V8ScriptValue qException = unwrapped ? unwrapped->toV8Value() : QScriptEngine::newVariant(exception.toVariant()); + return raiseException(qException);*/ +} + +ScriptValue ScriptEngineV8::create(int type, const void* ptr) { + QVariant variant(type, ptr); + V8ScriptValue scriptValue = castVariantToValue(variant); + return ScriptValue(new ScriptValueV8Wrapper(this, std::move(scriptValue))); +} + +QVariant ScriptEngineV8::convert(const ScriptValue& value, int typeId) { + ScriptValueV8Wrapper* unwrapped = ScriptValueV8Wrapper::unwrap(value); + if (unwrapped == nullptr) { + return QVariant(); + } + + QVariant var; + if (!castValueToVariant(unwrapped->toV8Value(), var, typeId)) { + return QVariant(); + } + + int destType = var.userType(); + if (destType != typeId) { + var.convert(typeId); // if conversion fails then var is set to QVariant() + } + + return var; +} diff --git a/libraries/script-engine/src/qtscript/ScriptEngineQtScript.h b/libraries/script-engine/src/v8/ScriptEngineV8.h similarity index 66% rename from libraries/script-engine/src/qtscript/ScriptEngineQtScript.h rename to libraries/script-engine/src/v8/ScriptEngineV8.h index 283672cb7d..c9bd7a80b4 100644 --- a/libraries/script-engine/src/qtscript/ScriptEngineQtScript.h +++ b/libraries/script-engine/src/v8/ScriptEngineV8.h @@ -1,10 +1,12 @@ // -// ScriptEngineQtScript.h +// ScriptEngineV8.h // libraries/script-engine/src/qtscript // // Created by Brad Hefta-Gaub on 12/14/13. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2022 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -13,8 +15,8 @@ /// @addtogroup ScriptEngine /// @{ -#ifndef hifi_ScriptEngineQtScript_h -#define hifi_ScriptEngineQtScript_h +#ifndef hifi_ScriptEngineV8_h +#define hifi_ScriptEngineV8_h #include @@ -27,29 +29,33 @@ #include #include -#include +#include "libplatform/libplatform.h" +#include "v8.h" #include "../ScriptEngine.h" #include "../ScriptManager.h" +#include "V8Types.h" #include "ArrayBufferClass.h" -class ScriptContextQtWrapper; -class ScriptEngineQtScript; +class ScriptContextV8Wrapper; +class ScriptEngineV8; class ScriptManager; -class ScriptObjectQtProxy; -using ScriptContextQtPointer = std::shared_ptr; +class ScriptObjectV8Proxy; +class ScriptMethodV8Proxy; +using ScriptContextQtPointer = std::shared_ptr; + +const double GARBAGE_COLLECTION_TIME_LIMIT_S = 1.0; Q_DECLARE_METATYPE(ScriptEngine::FunctionSignature) -/// [QtScript] Implements ScriptEngine for QtScript and translates calls for QScriptEngine -class ScriptEngineQtScript final : public QScriptEngine, - public ScriptEngine, - public std::enable_shared_from_this { +/// [V8] Implements ScriptEngine for V8 and translates calls for QScriptEngine +class ScriptEngineV8 final : public ScriptEngine, + public std::enable_shared_from_this { Q_OBJECT public: // construction - ScriptEngineQtScript(ScriptManager* scriptManager = nullptr); + ScriptEngineV8(ScriptManager* scriptManager = nullptr); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can @@ -66,7 +72,8 @@ public: // ScriptEngine implementation virtual ScriptValue globalObject() const override; virtual bool hasUncaughtException() const override; virtual bool isEvaluating() const override; - virtual ScriptValue lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1) override; + //virtual ScriptValue lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1) override; + virtual ScriptValue cheskScriptSyntax(ScriptProgramPointer program) override; virtual ScriptValue makeError(const ScriptValue& other, const QString& type = "Error") override; virtual ScriptManager* manager() const override; @@ -77,6 +84,9 @@ public: // ScriptEngine implementation virtual ScriptValue newArrayBuffer(const QByteArray& message) override; virtual ScriptValue newFunction(ScriptEngine::FunctionSignature fun, int length = 0) override; virtual ScriptValue newObject() override; + //virtual ScriptValue newObject( v8::Local ); + virtual ScriptValue newMethod(QObject* object, V8ScriptValue lifetime, + const QList& metas, int numMaxParams); virtual ScriptProgramPointer newProgram(const QString& sourceCode, const QString& fileName) override; virtual ScriptValue newQObject(QObject *object, ScriptEngine::ValueOwnership ownership = ScriptEngine::QtOwnership, const ScriptEngine::QObjectWrapOptions& options = ScriptEngine::QObjectWrapOptions()) override; @@ -104,7 +114,8 @@ public: // ScriptEngine implementation const QString& parent = QString("")) override; Q_INVOKABLE virtual void registerGlobalObject(const QString& name, QObject* object) override; virtual void setDefaultPrototype(int metaTypeId, const ScriptValue& prototype) override; - virtual void setObjectName(const QString& name) override; + // Already implemented by QObject + //virtual void setObjectName(const QString& name) override; virtual bool setProperty(const char* name, const QVariant& value) override; virtual void setProcessEventsInterval(int interval) override; virtual QThread* thread() const override; @@ -114,71 +125,88 @@ public: // ScriptEngine implementation virtual QStringList uncaughtExceptionBacktrace() const override; virtual int uncaughtExceptionLineNumber() const override; virtual void updateMemoryCost(const qint64& deltaSize) override; - virtual void requestCollectGarbage() override { collectGarbage(); } + virtual void requestCollectGarbage() override { while(!_v8Isolate->IdleNotificationDeadline(getV8Platform()->MonotonicallyIncreasingTime() + GARBAGE_COLLECTION_TIME_LIMIT_S)) {}; } // 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 - QScriptValue makeError(const QScriptValue& other = QScriptValue(), const QString& type = "Error"); + V8ScriptValue makeError(const V8ScriptValue& other, 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); + bool raiseException(const V8ScriptValue& 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: // public non-interface methods for other QtScript-specific classes to use + /*typedef V8ScriptValue (*FunctionSignature)(v8::Local, ScriptEngineV8 *); + typedef V8ScriptValue (*FunctionWithArgSignature)(v8::Local, ScriptEngineV8 *, void *); /// registers a global getter/setter - Q_INVOKABLE void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, - QScriptEngine::FunctionSignature setter, const QString& parent = QString("")); + Q_INVOKABLE void registerGetterSetter(const QString& name, FunctionSignature getter, + FunctionSignature setter, const QString& parent = QString("")); /// register a global function - Q_INVOKABLE void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1); + Q_INVOKABLE void registerFunction(const QString& name, FunctionSignature fun, int numArguments = -1); /// register a function as a method on a previously registered global object - Q_INVOKABLE void registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature fun, - int numArguments = -1); + Q_INVOKABLE void registerFunction(const QString& parent, const QString& name, FunctionSignature fun, + int numArguments = -1);*/ /// registers a global object by name - Q_INVOKABLE void registerValue(const QString& valueName, QScriptValue value); + Q_INVOKABLE void registerValue(const QString& valueName, V8ScriptValue value); // NOTE - this is used by the TypedArray implementation. we need to review this for thread safety - inline ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; } + // V8TODO + //inline ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; } public: // not for public use, but I don't like how Qt strings this along with private friend functions virtual ScriptValue create(int type, const void* ptr) override; virtual QVariant convert(const ScriptValue& value, int typeId) override; virtual void registerCustomType(int type, ScriptEngine::MarshalFunction marshalFunc, ScriptEngine::DemarshalFunction demarshalFunc) override; - int computeCastPenalty(QScriptValue& val, int destTypeId); - bool castValueToVariant(const QScriptValue& val, QVariant& dest, int destTypeId); - QScriptValue castVariantToValue(const QVariant& val); - static QString valueType(const QScriptValue& val); + int computeCastPenalty(const V8ScriptValue& val, int destTypeId); + bool castValueToVariant(const V8ScriptValue& val, QVariant& dest, int destTypeId); + V8ScriptValue castVariantToValue(const QVariant& val); + static QString valueType(const V8ScriptValue& val); + v8::Isolate* getIsolate() {return _v8Isolate;} + v8::Local getContext() { + v8::EscapableHandleScope handleScope(_v8Isolate); + return handleScope.Escape(_v8Context.Get(_v8Isolate)); + } + const v8::Local getConstContext() const {return _v8Context.Get(_v8Isolate);} - using ObjectWrapperMap = QMap>; + using ObjectWrapperMap = QMap>; mutable QMutex _qobjectWrapperMapProtect; ObjectWrapperMap _qobjectWrapperMap; + protected: - // like `newFunction`, but allows mapping inline C++ lambdas with captures as callable QScriptValues + // like `newFunction`, but allows mapping inline C++ lambdas with captures as callable V8ScriptValues // 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); + ScriptValue newLambdaFunction(std::function operation, + const V8ScriptValue& data, + const ValueOwnership& ownership = AutoOwnership); void registerSystemTypes(); protected: + static QMutex _v8InitMutex; + static std::once_flag _v8InitOnceFlag; + static v8::Platform* getV8Platform(); + + v8::Isolate* _v8Isolate; + v8::UniquePersistent _v8Context; + struct CustomMarshal { ScriptEngine::MarshalFunction marshalFunc; ScriptEngine::DemarshalFunction demarshalFunc; }; using CustomMarshalMap = QHash; - using CustomPrototypeMap = QHash; + using CustomPrototypeMap = QHash; QPointer _scriptManager; @@ -188,29 +216,31 @@ protected: ScriptValue _nullValue; ScriptValue _undefinedValue; mutable ScriptContextQtPointer _currContext; + QThread *_currentThread; - ArrayBufferClass* _arrayBufferClass; + //V8TODO + //ArrayBufferClass* _arrayBufferClass; }; -// Lambda helps create callable QScriptValues out of std::functions: +// Lambda helps create callable V8ScriptValues 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(ScriptEngineV8* engine, + std::function operation, + V8ScriptValue data); ~Lambda(); public slots: - QScriptValue call(); + V8ScriptValue call(); QString toString() const; private: - ScriptEngineQtScript* engine; - std::function operation; - QScriptValue data; + ScriptEngineV8* _engine; + std::function _operation; + V8ScriptValue _data; }; -#endif // hifi_ScriptEngineQtScript_h +#endif // hifi_ScriptEngineV8_h /// @} diff --git a/libraries/script-engine/src/qtscript/ScriptEngineQtScript_cast.cpp b/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp similarity index 68% rename from libraries/script-engine/src/qtscript/ScriptEngineQtScript_cast.cpp rename to libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp index 8fc821b5d9..3e95f5a495 100644 --- a/libraries/script-engine/src/qtscript/ScriptEngineQtScript_cast.cpp +++ b/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp @@ -1,8 +1,9 @@ // -// ScriptEngineQtScript_cast.cpp -// libraries/script-engine/src/qtscript +// ScriptEngineV8_cast.cpp +// libraries/script-engine/src/v8 // // Created by Heather Anderson 12/9/2021 +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 // Copyright 2021 Vircadia contributors. // Copyright 2022 Overte e.V. // @@ -10,30 +11,31 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "ScriptEngineQtScript.h" +#include "ScriptEngineV8.h" #include #include #include -#include +#include "libplatform/libplatform.h" +#include "v8.h" #include "../ScriptEngineCast.h" #include "../ScriptValueIterator.h" -#include "ScriptObjectQtProxy.h" -#include "ScriptValueQtWrapper.h" +#include "ScriptObjectV8Proxy.h" +#include "ScriptValueV8Wrapper.h" -void ScriptEngineQtScript::setDefaultPrototype(int metaTypeId, const ScriptValue& prototype) { - ScriptValueQtWrapper* unwrappedPrototype = ScriptValueQtWrapper::unwrap(prototype); +void ScriptEngineV8::setDefaultPrototype(int metaTypeId, const ScriptValue& prototype) { + ScriptValueV8Wrapper* unwrappedPrototype = ScriptValueV8Wrapper::unwrap(prototype); if (unwrappedPrototype) { - const QScriptValue& scriptPrototype = unwrappedPrototype->toQtValue(); + const V8ScriptValue& scriptPrototype = unwrappedPrototype->toV8Value(); _customTypeProtect.lockForWrite(); _customPrototypes.insert(metaTypeId, scriptPrototype); _customTypeProtect.unlock(); } } -void ScriptEngineQtScript::registerCustomType(int type, +void ScriptEngineV8::registerCustomType(int type, ScriptEngine::MarshalFunction marshalFunc, ScriptEngine::DemarshalFunction demarshalFunc) { @@ -48,13 +50,13 @@ void ScriptEngineQtScript::registerCustomType(int type, Q_DECLARE_METATYPE(ScriptValue); -static QScriptValue ScriptValueToQScriptValue(QScriptEngine* engine, const ScriptValue& src) { - return ScriptValueQtWrapper::fullUnwrap(static_cast(engine), src); +static V8ScriptValue ScriptValueToV8ScriptValue(ScriptEngineV8* engine, const ScriptValue& src) { + return ScriptValueV8Wrapper::fullUnwrap(static_cast(engine), src); } -static void ScriptValueFromQScriptValue(const QScriptValue& src, ScriptValue& dest) { - ScriptEngineQtScript* engine = static_cast(src.engine()); - dest = ScriptValue(new ScriptValueQtWrapper(engine, src)); +static void ScriptValueFromV8ScriptValue(ScriptEngineV8* engine, const V8ScriptValue& src, ScriptValue& dest) { + //ScriptEngineV8* engine = static_cast(src.engine()); + dest = ScriptValue(new ScriptValueV8Wrapper(engine, src)); } static ScriptValue StringListToScriptValue(ScriptEngine* engine, const QStringList& src) { @@ -183,10 +185,10 @@ static bool JsonArrayFromScriptValue(const ScriptValue& src, QJsonArray& dest) { // QMetaType::QJsonArray -void ScriptEngineQtScript::registerSystemTypes() { - qScriptRegisterMetaType(this, ScriptValueToQScriptValue, ScriptValueFromQScriptValue); +void ScriptEngineV8::registerSystemTypes() { + //qScriptRegisterMetaType(this, ScriptValueToV8ScriptValue, ScriptValueFromV8ScriptValue); - scriptRegisterMetaType(this); + scriptRegisterMetaType(static_cast(this)); scriptRegisterMetaType(this); scriptRegisterMetaType(this); scriptRegisterMetaType(this); @@ -195,8 +197,9 @@ void ScriptEngineQtScript::registerSystemTypes() { scriptRegisterMetaType(this); } -int ScriptEngineQtScript::computeCastPenalty(QScriptValue& val, int destTypeId) { - if (val.isNumber()) { +int ScriptEngineV8::computeCastPenalty(const V8ScriptValue& v8Val, int destTypeId) { + const v8::Local val = v8Val.constGet(); + if (val->IsNumber()) { switch (destTypeId){ case QMetaType::Bool: // Conversion to bool is acceptable, but numbers are preferred @@ -226,7 +229,7 @@ int ScriptEngineQtScript::computeCastPenalty(QScriptValue& val, int destTypeId) // Other, not predicted cases return 5; } - } else if (val.isString() || val.isDate() || val.isRegExp()) { + } else if (val->IsString() || val->IsDate() || val->IsRegExp()) { switch (destTypeId){ case QMetaType::Bool: // Conversion from to bool should be avoided if possible, it's probably not what we want @@ -254,7 +257,7 @@ int ScriptEngineQtScript::computeCastPenalty(QScriptValue& val, int destTypeId) default: return 5; } - } else if (val.isBool() || val.isBoolean()) { + } else if (val->IsBoolean()) { switch (destTypeId){ case QMetaType::Bool: // Perfect case @@ -287,11 +290,12 @@ int ScriptEngineQtScript::computeCastPenalty(QScriptValue& val, int destTypeId) return 0; } -bool ScriptEngineQtScript::castValueToVariant(const QScriptValue& val, QVariant& dest, int destTypeId) { +bool ScriptEngineV8::castValueToVariant(const V8ScriptValue& v8Val, QVariant& dest, int destTypeId) { + const v8::Local val = v8Val.constGet(); // if we're not particularly interested in a specific type, try to detect if we're dealing with a registered type if (destTypeId == QMetaType::UnknownType) { - QObject* obj = ScriptObjectQtProxy::unwrap(val); + QObject* obj = ScriptObjectV8Proxy::unwrap(v8Val); if (obj) { for (const QMetaObject* metaObject = obj->metaObject(); metaObject; metaObject = metaObject->superClass()) { QByteArray typeName = QByteArray(metaObject->className()) + "*"; @@ -305,7 +309,7 @@ bool ScriptEngineQtScript::castValueToVariant(const QScriptValue& val, QVariant& } if (destTypeId == qMetaTypeId()) { - dest = QVariant::fromValue(ScriptValue(new ScriptValueQtWrapper(this, val))); + dest = QVariant::fromValue(ScriptValue(new ScriptValueV8Wrapper(this, v8Val))); return true; } @@ -321,93 +325,111 @@ bool ScriptEngineQtScript::castValueToVariant(const QScriptValue& val, QVariant& } if (demarshalFunc) { dest = QVariant(destTypeId, static_cast(NULL)); - ScriptValue wrappedVal(new ScriptValueQtWrapper(this, val)); + ScriptValue wrappedVal(new ScriptValueV8Wrapper(this, v8Val)); bool success = demarshalFunc(wrappedVal, const_cast(dest.constData())); if(!success) dest = QVariant(); return success; } else { switch (destTypeId) { case QMetaType::UnknownType: - if (val.isUndefined()) { + if (val->IsUndefined()) { dest = QVariant(); break; } - if (val.isNull()) { + if (val->IsNull()) { dest = QVariant::fromValue(nullptr); break; } - if (val.isBool()) { - dest = QVariant::fromValue(val.toBool()); + if (val->IsBoolean()) { + //V8TODO is it right isolate? What if value from different script engine is used here + dest = QVariant::fromValue(val->ToBoolean(_v8Isolate)->Value()); break; } - if (val.isString()) { - dest = QVariant::fromValue(val.toString()); + if (val->IsString()) { + //V8TODO is it right context? What if value from different script engine is used here + v8::String::Utf8Value string(_v8Isolate, val); + Q_ASSERT(*string != nullptr); + dest = QVariant::fromValue(QString(*string)); + //dest = QVariant::fromValue(val->ToString(_v8Context.Get(_v8Isolate)).ToLocalChecked()->); break; } - if (val.isNumber()) { - dest = QVariant::fromValue(val.toNumber()); + if (val->IsNumber()) { + dest = QVariant::fromValue(val->ToNumber(_v8Context.Get(_v8Isolate)).ToLocalChecked()->Value()); break; } { - QObject* obj = ScriptObjectQtProxy::unwrap(val); + QObject* obj = ScriptObjectV8Proxy::unwrap(v8Val); if (obj) { dest = QVariant::fromValue(obj); break; } } { - QVariant var = ScriptVariantQtProxy::unwrap(val); + QVariant var = ScriptVariantV8Proxy::unwrap(v8Val); if (var.isValid()) { dest = var; break; } } - dest = val.toVariant(); + // V8TODO + Q_ASSERT(false); + //dest = val->ToVariant(); break; case QMetaType::Bool: - dest = QVariant::fromValue(val.toBool()); + dest = QVariant::fromValue(val->ToBoolean(_v8Isolate)->Value()); break; case QMetaType::QDateTime: case QMetaType::QDate: - Q_ASSERT(val.isDate()); - dest = QVariant::fromValue(val.toDateTime()); + if (val->IsDate()){ + double timeMs = v8::Date::Cast(*val)->NumberValue(_v8Context.Get(_v8Isolate)).ToChecked(); + dest = QVariant::fromValue(QDateTime::fromMSecsSinceEpoch(timeMs)); + } else if (val->IsNumber()) { + //V8TODO should we automatically cast numbers to datetime? + dest = QVariant::fromValue(QDateTime::fromMSecsSinceEpoch(val->ToNumber(_v8Context.Get(_v8Isolate)).ToLocalChecked()->Value())); + } else { + return false; + } break; case QMetaType::UInt: case QMetaType::ULong: - if ( val.isArray() || val.isObject() ){ + if ( val->IsArray() || val->IsObject() ){ return false; } - dest = QVariant::fromValue(val.toUInt32()); + dest = QVariant::fromValue(val->ToUint32(_v8Context.Get(_v8Isolate)).ToLocalChecked()->Value()); break; case QMetaType::Int: case QMetaType::Long: case QMetaType::Short: - if ( val.isArray() || val.isObject() ){ + if ( val->IsArray() || val->IsObject() ){ return false; } - dest = QVariant::fromValue(val.toInt32()); + dest = QVariant::fromValue(val->ToInt32(_v8Context.Get(_v8Isolate)).ToLocalChecked()->Value()); break; case QMetaType::Double: case QMetaType::Float: case QMetaType::ULongLong: case QMetaType::LongLong: - if ( val.isArray() || val.isObject() ){ + if ( val->IsArray() || val->IsObject() ){ return false; } - dest = QVariant::fromValue(val.toNumber()); + dest = QVariant::fromValue(val->ToNumber(_v8Context.Get(_v8Isolate)).ToLocalChecked()->Value()); break; case QMetaType::QString: case QMetaType::QByteArray: - dest = QVariant::fromValue(val.toString()); + { + v8::String::Utf8Value string(_v8Isolate, val); + Q_ASSERT(*string != nullptr); + dest = QVariant::fromValue(QString(*string)); + } break; case QMetaType::UShort: - if ( val.isArray() || val.isObject() ){ + if ( val->IsArray() || val->IsObject() ){ return false; } - dest = QVariant::fromValue(val.toUInt16()); + dest = QVariant::fromValue(static_cast(val->ToUint32(_v8Context.Get(_v8Isolate)).ToLocalChecked()->Value())); break; case QMetaType::QObjectStar: - dest = QVariant::fromValue(ScriptObjectQtProxy::unwrap(val)); + dest = QVariant::fromValue(ScriptObjectV8Proxy::unwrap(v8Val)); break; default: // check to see if this is a pointer to a QObject-derived object @@ -417,7 +439,7 @@ bool ScriptEngineQtScript::castValueToVariant(const QScriptValue& val, QVariant& dest = QVariant::fromValue(nullptr); break; }*/ - QObject* obj = ScriptObjectQtProxy::unwrap(val); + QObject* obj = ScriptObjectV8Proxy::unwrap(v8Val); if (!obj) return false; const QMetaObject* destMeta = QMetaType::metaObjectForType(destTypeId); Q_ASSERT(destMeta); @@ -428,14 +450,16 @@ bool ScriptEngineQtScript::castValueToVariant(const QScriptValue& val, QVariant& } // check to see if we have a registered prototype { - QVariant var = ScriptVariantQtProxy::unwrap(val); + QVariant var = ScriptVariantV8Proxy::unwrap(v8Val); if (var.isValid()) { dest = var; break; } } // last chance, just convert it to a variant - dest = val.toVariant(); + // V8TODO + Q_ASSERT(false); + //dest = val->ToVariant(); break; } } @@ -443,24 +467,26 @@ bool ScriptEngineQtScript::castValueToVariant(const QScriptValue& val, QVariant& return destTypeId == QMetaType::UnknownType || dest.userType() == destTypeId || dest.convert(destTypeId); } -QString ScriptEngineQtScript::valueType(const QScriptValue& val) { - if (val.isUndefined()) { +QString ScriptEngineV8::valueType(const V8ScriptValue& v8Val) { + const v8::Local val = v8Val.constGet(); + + if (val->IsUndefined()) { return "undefined"; } - if (val.isNull()) { + if (val->IsNull()) { return "null"; } - if (val.isBool()) { + if (val->IsBoolean()) { return "boolean"; } - if (val.isString()) { + if (val->IsString()) { return "string"; } - if (val.isNumber()) { + if (val->IsNumber()) { return "number"; } { - QObject* obj = ScriptObjectQtProxy::unwrap(val); + QObject* obj = ScriptObjectV8Proxy::unwrap(v8Val); if (obj) { QString objectName = obj->objectName(); if (!objectName.isEmpty()) return objectName; @@ -468,21 +494,24 @@ QString ScriptEngineQtScript::valueType(const QScriptValue& val) { } } { - QVariant var = ScriptVariantQtProxy::unwrap(val); + QVariant var = ScriptVariantV8Proxy::unwrap(v8Val); if (var.isValid()) { return var.typeName(); } } - return val.toVariant().typeName(); + //V8TODO + Q_ASSERT(false); + //return val->toVariant().typeName(); + return "undefined"; } -QScriptValue ScriptEngineQtScript::castVariantToValue(const QVariant& val) { +V8ScriptValue ScriptEngineV8::castVariantToValue(const QVariant& val) { int valTypeId = val.userType(); if (valTypeId == qMetaTypeId()) { // this is a wrapped ScriptValue, so just unwrap it and call it good ScriptValue innerVal = val.value(); - return ScriptValueQtWrapper::fullUnwrap(this, innerVal); + return ScriptValueV8Wrapper::fullUnwrap(this, innerVal); } // do we have a registered handler for this type? @@ -497,61 +526,72 @@ QScriptValue ScriptEngineQtScript::castVariantToValue(const QVariant& val) { } if (marshalFunc) { ScriptValue wrappedVal = marshalFunc(this, val.constData()); - return ScriptValueQtWrapper::fullUnwrap(this, wrappedVal); + return ScriptValueV8Wrapper::fullUnwrap(this, wrappedVal); } switch (valTypeId) { case QMetaType::UnknownType: case QMetaType::Void: - return QScriptValue(this, QScriptValue::UndefinedValue); + return V8ScriptValue(_v8Isolate, v8::Undefined(_v8Isolate)); case QMetaType::Nullptr: - return QScriptValue(this, QScriptValue::NullValue); + return V8ScriptValue(_v8Isolate, v8::Null(_v8Isolate)); case QMetaType::Bool: - return QScriptValue(this, val.toBool()); + return V8ScriptValue(_v8Isolate, v8::Boolean::New(_v8Isolate, val.toBool())); case QMetaType::Int: case QMetaType::Long: case QMetaType::Short: - return QScriptValue(this, val.toInt()); + return V8ScriptValue(_v8Isolate, v8::Integer::New(_v8Isolate, val.toInt())); case QMetaType::UInt: - case QMetaType::ULong: case QMetaType::UShort: - return QScriptValue(this, val.toUInt()); - case QMetaType::Float: - case QMetaType::LongLong: + case QMetaType::ULong: + return V8ScriptValue(_v8Isolate, v8::Uint32::New(_v8Isolate, val.toUInt())); case QMetaType::ULongLong: + return V8ScriptValue(_v8Isolate, v8::Number::New(_v8Isolate, val.toULongLong())); + case QMetaType::LongLong: + return V8ScriptValue(_v8Isolate, v8::Number::New(_v8Isolate, val.toLongLong())); + case QMetaType::Float: case QMetaType::Double: - return QScriptValue(this, val.toFloat()); + return V8ScriptValue(_v8Isolate, v8::Number::New(_v8Isolate, val.toDouble())); case QMetaType::QString: case QMetaType::QByteArray: - return QScriptValue(this, val.toString()); + return V8ScriptValue(_v8Isolate, v8::String::NewFromUtf8(_v8Isolate, val.toString().toStdString().c_str()).ToLocalChecked()); case QMetaType::QVariant: return castVariantToValue(val.value()); case QMetaType::QObjectStar: { QObject* obj = val.value(); - if (obj == nullptr) return QScriptValue(this, QScriptValue::NullValue); - return ScriptObjectQtProxy::newQObject(this, obj); + if (obj == nullptr) return V8ScriptValue(_v8Isolate, v8::Null(_v8Isolate)); + return ScriptObjectV8Proxy::newQObject(this, obj); } case QMetaType::QDateTime: - return static_cast(this)->newDate(val.value()); + { + double timeMs = val.value().currentMSecsSinceEpoch(); + return V8ScriptValue(_v8Isolate, v8::Date::New(_v8Context.Get(_v8Isolate), timeMs).ToLocalChecked()); + } case QMetaType::QDate: - return static_cast(this)->newDate(val.value().startOfDay()); + { + double timeMs = val.value().startOfDay().currentMSecsSinceEpoch(); + return V8ScriptValue(_v8Isolate, v8::Date::New(_v8Context.Get(_v8Isolate), timeMs).ToLocalChecked()); + } default: // check to see if this is a pointer to a QObject-derived object if (QMetaType::typeFlags(valTypeId) & (QMetaType::PointerToQObject | QMetaType::TrackingPointerToQObject)) { QObject* obj = val.value(); - if (obj == nullptr) return QScriptValue(this, QScriptValue::NullValue); - return ScriptObjectQtProxy::newQObject(this, obj); + if (obj == nullptr) return V8ScriptValue(_v8Isolate, v8::Null(_v8Isolate)); + return ScriptObjectV8Proxy::newQObject(this, obj); } // have we set a prototype'd variant? { _customTypeProtect.lockForRead(); CustomPrototypeMap::const_iterator lookup = _customPrototypes.find(valTypeId); if (lookup != _customPrototypes.cend()) { - return ScriptVariantQtProxy::newVariant(this, val, lookup.value()); + return ScriptVariantV8Proxy::newVariant(this, val, lookup.value()); } _customTypeProtect.unlock(); } // just do a generic variant - return QScriptEngine::newVariant(val); + //V8TODO + Q_ASSERT(false); + return V8ScriptValue(_v8Isolate, v8::Undefined(_v8Isolate)); + //return QScriptEngine::newVariant(val); } } diff --git a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp new file mode 100644 index 0000000000..232a372708 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp @@ -0,0 +1,1105 @@ +// +// ScriptObjectV8Proxy.cpp +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 12/5/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptObjectV8Proxy.h" + +#include +#include + +#include "../ScriptEngineLogging.h" + +#include "ScriptContextV8Wrapper.h" +#include "ScriptValueV8Wrapper.h" + +Q_DECLARE_METATYPE(V8ScriptContext*) +Q_DECLARE_METATYPE(ScriptValue) +//V8TODO? +//Q_DECLARE_METATYPE(V8ScriptValue) + +Q_DECLARE_METATYPE(QSharedPointer) +Q_DECLARE_METATYPE(QSharedPointer) + +// Value of internal field with index 0 when object contains ScriptObjectV8Proxy pointer in internal field 1 +static const void *internalPointsToQObjectProxy = (void *)0x13370000; +static const void *internalPointsToQVariantProxy = (void *)0x13371000; +static const void *internalPointsToSignalProxy = (void *)0x13372000; +static const void *internalPointsToMethodProxy = (void *)0x13372000; + +// Used strictly to replace the "this" object value for property access. May expand to a full context element +// if we find it necessary to, but hopefully not needed +class ScriptPropertyContextQtWrapper final : public ScriptContext { +public: // construction + inline ScriptPropertyContextQtWrapper(const ScriptValue& object, ScriptContext* parentContext) : + _parent(parentContext), _object(object) {} + +public: // ScriptContext implementation + virtual int argumentCount() const override { return _parent->argumentCount(); } + virtual ScriptValue argument(int index) const override { return _parent->argument(index); } + virtual QStringList backtrace() const override { return _parent->backtrace(); } + virtual ScriptValue callee() const override { return _parent->callee(); } + virtual ScriptEnginePointer engine() const override { return _parent->engine(); } + virtual ScriptFunctionContextPointer functionContext() const override { return _parent->functionContext(); } + virtual ScriptContextPointer parentContext() const override { return _parent->parentContext(); } + virtual ScriptValue thisObject() const override { return _object; } + virtual ScriptValue throwError(const QString& text) override { return _parent->throwError(text); } + virtual ScriptValue throwValue(const ScriptValue& value) override { return _parent->throwValue(value); } + +private: // storage + ScriptContext* _parent; + const ScriptValue& _object; +}; + +ScriptObjectV8Proxy::ScriptObjectV8Proxy(ScriptEngineV8* engine, QObject* object, bool ownsObject, const ScriptEngine::QObjectWrapOptions& options) : + _engine(engine), _wrapOptions(options), _ownsObject(ownsObject), _object(object), + _v8ObjectTemplate(engine->getIsolate(), v8::ObjectTemplate::New(engine->getIsolate())) { + investigate(); +} + +V8ScriptValue ScriptObjectV8Proxy::newQObject(ScriptEngineV8* engine, QObject* object, + ScriptEngine::ValueOwnership ownership, + const ScriptEngine::QObjectWrapOptions& options) { + // do we already have a valid wrapper for this QObject? + { + QMutexLocker guard(&engine->_qobjectWrapperMapProtect); + ScriptEngineV8::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object); + if (lookup != engine->_qobjectWrapperMap.end()) { + QSharedPointer proxy = lookup.value().lock(); + // V8TODO: is conversion to QVariant and back needed? + if (proxy) { + return V8ScriptValue(engine->getIsolate(), proxy.get()->toV8Value()); + } + //if (proxy) return engine->newObject(proxy.get(), engine->newVariant(QVariant::fromValue(proxy)));; + } + } + + bool ownsObject; + switch (ownership) { + case ScriptEngine::QtOwnership: + ownsObject = false; + break; + case ScriptEngine::ScriptOwnership: + ownsObject = true; + break; + case ScriptEngine::AutoOwnership: + ownsObject = !object->parent(); + break; + default: + ownsObject = false; + qCritical() << "Wrong ScriptEngine::ValueOwnership value: " << ownership; + break; + } + + // create the wrapper + auto proxy = QSharedPointer::create(engine, object, ownsObject, options); + + { + QMutexLocker guard(&engine->_qobjectWrapperMapProtect); + + // check again to see if someone else created the wrapper while we were busy + ScriptEngineV8::ObjectWrapperMap::const_iterator lookup = engine->_qobjectWrapperMap.find(object); + if (lookup != engine->_qobjectWrapperMap.end()) { + QSharedPointer proxy = lookup.value().lock(); + //if (proxy) return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy)));; + if (proxy) { + return V8ScriptValue(engine->getIsolate(), proxy.get()->toV8Value()); + } + } + + // register the wrapper with the engine and make sure it cleans itself up + engine->_qobjectWrapperMap.insert(object, proxy); + QPointer enginePtr = engine; + object->connect(object, &QObject::destroyed, engine, [enginePtr, object]() { + if (!enginePtr) return; + QMutexLocker guard(&enginePtr->_qobjectWrapperMapProtect); + ScriptEngineV8::ObjectWrapperMap::iterator lookup = enginePtr->_qobjectWrapperMap.find(object); + if (lookup != enginePtr->_qobjectWrapperMap.end()) { + enginePtr->_qobjectWrapperMap.erase(lookup); + } + }); + } + + return V8ScriptValue(engine->getIsolate(), proxy.get()->toV8Value()); + //return qengine->newObject(proxy.get(), qengine->newVariant(QVariant::fromValue(proxy))); +} + +ScriptObjectV8Proxy* ScriptObjectV8Proxy::unwrapProxy(const V8ScriptValue& val) { + auto v8Value = val.constGet(); + if (!v8Value->IsObject()) { + return nullptr; + } + v8::Local v8Object = v8Value->ToObject(val.constGetContext()).ToLocalChecked(); + if (v8Object->InternalFieldCount() != 2) { + return nullptr; + } + if (v8Object->GetAlignedPointerFromInternalField(0) == internalPointsToQObjectProxy) { + return nullptr; + } + return reinterpret_cast(v8Object->GetAlignedPointerFromInternalField(1)); +} + +QObject* ScriptObjectV8Proxy::unwrap(const V8ScriptValue& val) { + ScriptObjectV8Proxy* proxy = unwrapProxy(val); + return proxy ? proxy->toQObject() : nullptr; +} + +ScriptObjectV8Proxy::~ScriptObjectV8Proxy() { + if (_ownsObject) { + QObject* qobject = _object; + if(qobject) qobject->deleteLater(); + } +} + +void ScriptObjectV8Proxy::investigate() { + QObject* qobject = _object; + Q_ASSERT(qobject); + if (!qobject) return; + + auto objectTemplate = _v8ObjectTemplate.Get(_engine->getIsolate()); + objectTemplate->SetInternalFieldCount(3); + objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration(v8Get, v8Set)); + + const QMetaObject* metaObject = qobject->metaObject(); + + // discover properties + int startIdx = _wrapOptions & ScriptEngine::ExcludeSuperClassProperties ? metaObject->propertyOffset() : 0; + int num = metaObject->propertyCount(); + for (int idx = startIdx; idx < num; ++idx) { + QMetaProperty prop = metaObject->property(idx); + if (!prop.isScriptable()) continue; + + // always exclude child objects (at least until we decide otherwise) + int metaTypeId = prop.userType(); + if (metaTypeId != QMetaType::UnknownType) { + QMetaType metaType(metaTypeId); + if (metaType.flags() & QMetaType::PointerToQObject) { + continue; + } + } + + auto v8Name = v8::String::NewFromUtf8(_engine->getIsolate(), prop.name()).ToLocalChecked(); + PropertyDef& propDef = _props.insert(idx, PropertyDef(_engine->getIsolate(), v8Name)).value(); + propDef.flags = ScriptValue::Undeletable | ScriptValue::PropertyGetter | ScriptValue::PropertySetter | + ScriptValue::QObjectMember; + if (prop.isConstant()) propDef.flags |= ScriptValue::ReadOnly; + } + + // discover methods + startIdx = (_wrapOptions & ScriptEngine::ExcludeSuperClassMethods) ? metaObject->methodOffset() : 0; + num = metaObject->methodCount(); + QHash methodNames; + for (int idx = startIdx; idx < num; ++idx) { + QMetaMethod method = metaObject->method(idx); + + // perhaps keep this comment? Calls (like AudioScriptingInterface::playSound) seem to expect non-public methods to be script-accessible + /* if (method.access() != QMetaMethod::Public) continue;*/ + + bool isSignal = false; + QByteArray szName = method.name(); + + switch (method.methodType()) { + case QMetaMethod::Constructor: + continue; + case QMetaMethod::Signal: + isSignal = true; + break; + case QMetaMethod::Slot: + if (_wrapOptions & ScriptEngine::ExcludeSlots) { + continue; + } + if (szName == "deleteLater") { + continue; + } + break; + default: + break; + } + + V8ScriptString name(_engine->getIsolate(), v8::String::NewFromUtf8(_engine->getIsolate(), szName.data(), v8::NewStringType::kNormal, szName.length()).ToLocalChecked()); + auto nameLookup = methodNames.find(name); + if (isSignal) { + if (nameLookup == methodNames.end()) { + SignalDef& signalDef = _signals.insert(idx, SignalDef(_engine->getIsolate(), name.get())).value(); + signalDef.name = name; + signalDef.signal = method; + methodNames.insert(name, idx); + } else { + int originalMethodId = nameLookup.value(); + SignalDefMap::iterator signalLookup = _signals.find(originalMethodId); + Q_ASSERT(signalLookup != _signals.end()); + SignalDef& signalDef = signalLookup.value(); + Q_ASSERT(signalDef.signal.parameterCount() != method.parameterCount()); + if (signalDef.signal.parameterCount() < method.parameterCount()) { + signalDef.signal = method; + } + } + } else { + int parameterCount = method.parameterCount(); + if(method.returnType() == QMetaType::UnknownType) { + qCritical(scriptengine) << "Method " << metaObject->className() << "::" << name.toQString() << " has QMetaType::UnknownType return value"; + } + for (int i = 0; i < method.parameterCount(); i++) { + if (method.parameterType(i) == QMetaType::UnknownType) { + qCritical(scriptengine) << "Parameter " << i << "in method " << metaObject->className() << "::" << name.toQString() << " is of type QMetaType::UnknownType"; + } + } + if (nameLookup == methodNames.end()) { + MethodDef& methodDef = _methods.insert(idx, MethodDef(_engine->getIsolate(), name.get())).value(); + methodDef.name = name; + methodDef.numMaxParms = parameterCount; + methodDef.methods.append(method); + methodNames.insert(name, idx); + } else { + int originalMethodId = nameLookup.value(); + MethodDefMap::iterator methodLookup = _methods.find(originalMethodId); + Q_ASSERT(methodLookup != _methods.end()); + MethodDef& methodDef = methodLookup.value(); + if(methodDef.numMaxParms < parameterCount) methodDef.numMaxParms = parameterCount; + methodDef.methods.append(method); + } + } + } + + v8::Local v8Object = objectTemplate->NewInstance(_engine->getContext()).ToLocalChecked(); + v8Object->SetAlignedPointerInInternalField(0, const_cast(internalPointsToQObjectProxy)); + v8Object->SetAlignedPointerInInternalField(1, reinterpret_cast(this)); + + _v8Object.Reset(_engine->getIsolate(), v8Object); +} + +QString ScriptObjectV8Proxy::name() const { + Q_ASSERT(_object); + if (!_object) return ""; + return _object ? _object->objectName() : ""; + QString objectName = _object->objectName(); + if (!objectName.isEmpty()) return objectName; + return _object->metaObject()->className(); +} + +ScriptObjectV8Proxy::QueryFlags ScriptObjectV8Proxy::queryProperty(const V8ScriptValue& object, const V8ScriptString& name, QueryFlags flags, uint* id) { + // check for properties + for (PropertyDefMap::const_iterator trans = _props.cbegin(); trans != _props.cend(); ++trans) { + const PropertyDef& propDef = trans.value(); + if (propDef.name.constGet() != name.constGet()) continue; + *id = trans.key() | PROPERTY_TYPE; + return flags & (HandlesReadAccess | HandlesWriteAccess); + } + + // check for methods + for (MethodDefMap::const_iterator trans = _methods.cbegin(); trans != _methods.cend(); ++trans) { + if (trans.value().name.constGet() != name.constGet()) continue; + *id = trans.key() | METHOD_TYPE; + return flags & (HandlesReadAccess | HandlesWriteAccess); + } + + // check for signals + for (SignalDefMap::const_iterator trans = _signals.cbegin(); trans != _signals.cend(); ++trans) { + if (trans.value().name.constGet() != name.constGet()) continue; + *id = trans.key() | SIGNAL_TYPE; + return flags & (HandlesReadAccess | HandlesWriteAccess); + } + + return QueryFlags(); +} + +ScriptValue::PropertyFlags ScriptObjectV8Proxy::propertyFlags(const V8ScriptValue& object, const V8ScriptString& name, uint id) { + QObject* qobject = _object; + if (!qobject) { + return ScriptValue::PropertyFlags(); + } + + switch (id & TYPE_MASK) { + case PROPERTY_TYPE: { + PropertyDefMap::const_iterator lookup = _props.find(id & ~TYPE_MASK); + if (lookup == _props.cend()) return ScriptValue::PropertyFlags(); + const PropertyDef& propDef = lookup.value(); + return propDef.flags; + } + case METHOD_TYPE: { + MethodDefMap::const_iterator lookup = _methods.find(id & ~TYPE_MASK); + if (lookup == _methods.cend()) return ScriptValue::PropertyFlags(); + return ScriptValue::ReadOnly | ScriptValue::Undeletable | ScriptValue::QObjectMember; + } + case SIGNAL_TYPE: { + SignalDefMap::const_iterator lookup = _signals.find(id & ~TYPE_MASK); + if (lookup == _signals.cend()) return ScriptValue::PropertyFlags(); + return ScriptValue::ReadOnly | ScriptValue::Undeletable | ScriptValue::QObjectMember; + } + } + return ScriptValue::PropertyFlags(); +} + +void ScriptObjectV8Proxy::v8Get(v8::Local name, const v8::PropertyCallbackInfo& info) { + // V8TODO + //info.GetReturnValue().Set(); +} + +void ScriptObjectV8Proxy::v8Set(v8::Local name, v8::Local value, const v8::PropertyCallbackInfo& info) { + // V8TODO + info.GetReturnValue().Set(value); +} + + +V8ScriptValue ScriptObjectV8Proxy::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { + QObject* qobject = _object; + if (!qobject) { + _engine->getIsolate()->ThrowError("Referencing deleted native object"); + return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); + } + + const QMetaObject* metaObject = qobject->metaObject(); + + switch (id & TYPE_MASK) { + case PROPERTY_TYPE: { + int propId = id & ~TYPE_MASK; + PropertyDefMap::const_iterator lookup = _props.find(propId); + if (lookup == _props.cend()) return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); + + QMetaProperty prop = metaObject->property(propId); + ScriptValue scriptThis = ScriptValue(new ScriptValueV8Wrapper(_engine, object)); + ScriptPropertyContextQtWrapper ourContext(scriptThis, _engine->currentContext()); + ScriptContextGuard guard(&ourContext); + + QVariant varValue = prop.read(qobject); + return _engine->castVariantToValue(varValue); + } + case METHOD_TYPE: { + int methodId = id & ~TYPE_MASK; + MethodDefMap::const_iterator lookup = _methods.find(methodId); + if (lookup == _methods.cend()) return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); + const MethodDef& methodDef = lookup.value(); + for (auto iter = methodDef.methods.begin(); iter != methodDef.methods.end(); iter++ ) { + if((*iter).returnType() == QMetaType::UnknownType) { + qDebug(scriptengine) << "Method with QMetaType::UnknownType " << metaObject->className() << " " << (*iter).name(); + } + } + return ScriptMethodV8Proxy::newMethod(_engine, qobject, object, methodDef.methods, methodDef.numMaxParms); + } + case SIGNAL_TYPE: { + int signalId = id & ~TYPE_MASK; + SignalDefMap::const_iterator defLookup = _signals.find(signalId); + if (defLookup == _signals.cend()) return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); + + InstanceMap::const_iterator instLookup = _signalInstances.find(signalId); + if (instLookup == _signalInstances.cend() || instLookup.value().isNull()) { + instLookup = _signalInstances.insert(signalId, + new ScriptSignalV8Proxy(_engine, qobject, object, defLookup.value().signal)); + Q_ASSERT(instLookup != _signalInstances.cend()); + } + ScriptSignalV8Proxy* proxy = instLookup.value(); + + ScriptEngine::QObjectWrapOptions options = ScriptEngine::ExcludeSuperClassContents | + //V8TODO ScriptEngine::ExcludeDeleteLater | + ScriptEngine::PreferExistingWrapperObject; + return ScriptObjectV8Proxy::newQObject(_engine, proxy, ScriptEngine::ScriptOwnership, options); + //return _engine->newQObject(proxy, ScriptEngine::ScriptOwnership, options); + } + } + return V8ScriptValue(_engine->getIsolate(), v8::Null(_engine->getIsolate())); +} + +void ScriptObjectV8Proxy::setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) { + if (!(id & PROPERTY_TYPE)) return; + QObject* qobject = _object; + if (!qobject) { + _engine->getIsolate()->ThrowError("Referencing deleted native object"); + return; + } + + int propId = id & ~TYPE_MASK; + PropertyDefMap::const_iterator lookup = _props.find(propId); + if (lookup == _props.cend()) return; + const PropertyDef& propDef = lookup.value(); + if (propDef.flags & ScriptValue::ReadOnly) return; + + const QMetaObject* metaObject = qobject->metaObject(); + QMetaProperty prop = metaObject->property(propId); + + ScriptValue scriptThis = ScriptValue(new ScriptValueV8Wrapper(_engine, object)); + ScriptPropertyContextQtWrapper ourContext(scriptThis, _engine->currentContext()); + ScriptContextGuard guard(&ourContext); + + int propTypeId = prop.userType(); + Q_ASSERT(propTypeId != QMetaType::UnknownType); + QVariant varValue; + if(!_engine->castValueToVariant(value, varValue, propTypeId)) { + QByteArray propTypeName = QMetaType(propTypeId).name(); + QByteArray valTypeName = _engine->valueType(value).toLatin1(); + _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Cannot convert %1 to %2").arg(valTypeName, propTypeName).toStdString().c_str()).ToLocalChecked()); + return; + } + prop.write(qobject, varValue); +} + +ScriptVariantV8Proxy::ScriptVariantV8Proxy(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue scriptProto, ScriptObjectV8Proxy* proto) : + _engine(engine), _variant(variant), _scriptProto(scriptProto), _proto(proto) { + _name = QString::fromLatin1(variant.typeName()); +} + +V8ScriptValue ScriptVariantV8Proxy::newVariant(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue proto) { + ScriptObjectV8Proxy* protoProxy = ScriptObjectV8Proxy::unwrapProxy(proto); + if (!protoProxy) { + Q_ASSERT(protoProxy); + //return engine->newVariant(variant); + return V8ScriptValue(engine->getIsolate(), v8::Undefined(engine->getIsolate())); + } + // V8TODO probably needs connection to be deleted + // V8TODO what to do with proto variable? + auto proxy = new ScriptVariantV8Proxy(engine, variant, proto, protoProxy); + auto variantDataTemplate = v8::ObjectTemplate::New(engine->getIsolate()); + variantDataTemplate->SetInternalFieldCount(2); + auto variantData = variantDataTemplate->NewInstance(engine->getContext()).ToLocalChecked(); + variantData->SetAlignedPointerInInternalField(0, const_cast(internalPointsToQVariantProxy)); + variantData->SetAlignedPointerInInternalField(1, reinterpret_cast(proxy)); + return V8ScriptValue(engine->getIsolate(), variantData); +} + +ScriptVariantV8Proxy* ScriptVariantV8Proxy::unwrapProxy(const V8ScriptValue& val) { + auto v8Value = val.constGet(); + if (!v8Value->IsObject()) { + return nullptr; + } + v8::Local v8Object = v8Value->ToObject(val.constGetContext()).ToLocalChecked(); + if (v8Object->InternalFieldCount() != 2) { + return nullptr; + } + if (v8Object->GetAlignedPointerFromInternalField(0) == internalPointsToQVariantProxy) { + return nullptr; + } + return reinterpret_cast(v8Object->GetAlignedPointerFromInternalField(1)); +} + +QVariant ScriptVariantV8Proxy::unwrap(const V8ScriptValue& val) { + ScriptVariantV8Proxy* proxy = unwrapProxy(val); + return proxy ? proxy->toQVariant() : QVariant(); +} + +ScriptMethodV8Proxy::ScriptMethodV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, + const QList& metas, int numMaxParms) : + _numMaxParms(numMaxParms), _engine(engine), _object(object), _objectLifetime(lifetime), _metas(metas) { +} + +V8ScriptValue ScriptMethodV8Proxy::newMethod(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, + const QList& metas, int numMaxParams) { + auto methodDataTemplate = v8::ObjectTemplate::New(engine->getIsolate()); + methodDataTemplate->SetInternalFieldCount(2); + auto methodData = methodDataTemplate->NewInstance(engine->getContext()).ToLocalChecked(); + methodData->SetAlignedPointerInInternalField(0, const_cast(internalPointsToMethodProxy)); + // V8TODO it needs to be deleted somehow on object destruction + methodData->SetAlignedPointerInInternalField(1, reinterpret_cast(new ScriptMethodV8Proxy(engine, object, lifetime, metas, numMaxParams))); + auto v8Function = v8::Function::New(engine->getContext(), callback, methodData, numMaxParams).ToLocalChecked(); + return V8ScriptValue(engine->getIsolate(), v8Function); +} + +QString ScriptMethodV8Proxy::fullName() const { + Q_ASSERT(_object); + if (!_object) return ""; + Q_ASSERT(!_metas.isEmpty()); + const QMetaMethod& firstMethod = _metas.front(); + QString objectName = _object->objectName(); + if (!objectName.isEmpty()) { + return QString("%1.%2").arg(objectName, firstMethod.name()); + } + return QString("%1::%2").arg(_object->metaObject()->className(), firstMethod.name()); +} + +// V8TODO +/*bool ScriptMethodV8Proxy::supportsExtension(Extension extension) const { + switch (extension) { + case Callable: + return true; + default: + return false; + } +}*/ + +void ScriptMethodV8Proxy::callback(const v8::FunctionCallbackInfo& arguments) { + if (!arguments.This()->IsObject()) { + arguments.GetIsolate()->ThrowError("Method value is not an object"); + return; + } + if (!arguments.This()->IsCallable()) { + arguments.GetIsolate()->ThrowError("Method value is not callable"); + return; + } + if (arguments.This()->InternalFieldCount() != 2) { + arguments.GetIsolate()->ThrowError("Incorrect number of internal fields during method call"); + return; + } + if (arguments.This()->GetAlignedPointerFromInternalField(0) == internalPointsToMethodProxy) { + arguments.GetIsolate()->ThrowError("Internal field 0 of ScriptMethodV8Proxy V8 object has wrong value"); + return; + } + ScriptMethodV8Proxy *proxy = reinterpret_cast(arguments.This()->GetAlignedPointerFromInternalField(1)); + proxy->call(arguments); +} + +void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo& arguments) { + QObject* qobject = _object; + v8::Isolate *isolate = arguments.GetIsolate(); + if (!qobject) { + isolate->ThrowError("Referencing deleted native object"); + return; + } + + int scriptNumArgs = arguments.Length(); + int numArgs = std::min(scriptNumArgs, _numMaxParms); + + const int scriptValueTypeId = qMetaTypeId(); + + int parameterConversionFailureId = 0; + int parameterConversionFailureCount = 0; + + int num_metas = _metas.size(); + QVector< QList > qScriptArgLists; + QVector< QVector > qGenArgsVectors; + QVector< QList > qVarArgLists; + qScriptArgLists.resize(num_metas); + qGenArgsVectors.resize(num_metas); + qVarArgLists.resize(num_metas); + bool isValidMetaSelected = false; + int bestMeta = 0; + int bestConversionPenaltyScore = 0; + + for (int i = 0; i < num_metas; i++) { + const QMetaMethod& meta = _metas[i]; + int methodNumArgs = meta.parameterCount(); + if (methodNumArgs != numArgs) { + continue; + } + + qGenArgsVectors[i].resize(10); + int conversionPenaltyScore = 0; + int conversionFailures = 0; + + for (int arg = 0; arg < numArgs; ++arg) { + int methodArgTypeId = meta.parameterType(arg); + Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); + v8::Local argVal = arguments[arg]; + if (methodArgTypeId == scriptValueTypeId) { + qScriptArgLists[i].append(ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(isolate, argVal)))); + qGenArgsVectors[i][arg] = Q_ARG(ScriptValue, qScriptArgLists[i].back()); + } else if (methodArgTypeId == QMetaType::QVariant) { + QVariant varArgVal; + if (!_engine->castValueToVariant(V8ScriptValue(isolate, argVal), varArgVal, methodArgTypeId)) { + conversionFailures++; + } else { + qVarArgLists[i].append(varArgVal); + qGenArgsVectors[i][arg] = Q_ARG(QVariant, qVarArgLists[i].back()); + } + } else { + QVariant varArgVal; + if (!_engine->castValueToVariant(V8ScriptValue(isolate, argVal), varArgVal, methodArgTypeId)) { + conversionFailures++; + } else { + qVarArgLists[i].append(varArgVal); + const QVariant& converted = qVarArgLists[i].back(); + conversionPenaltyScore = _engine->computeCastPenalty(V8ScriptValue(isolate, argVal), methodArgTypeId); + + // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant + // A const_cast is needed because calling data() would detach the QVariant. + qGenArgsVectors[i][arg] = + QGenericArgument(QMetaType::typeName(converted.userType()), const_cast(converted.constData())); + } + } + } + if (conversionFailures) { + if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) { + parameterConversionFailureCount = conversionFailures; + parameterConversionFailureId = meta.methodIndex(); + } + continue; + } + + if (!isValidMetaSelected) { + isValidMetaSelected = true; + bestMeta = i; + bestConversionPenaltyScore = conversionPenaltyScore; + } + if (isValidMetaSelected && bestConversionPenaltyScore > conversionPenaltyScore) { + bestMeta = i; + bestConversionPenaltyScore = conversionPenaltyScore; + } + } + + if (isValidMetaSelected) { + //ScriptContextV8Wrapper ourContext(_engine, context); + //ScriptContextGuard guard(&ourContext); + const QMetaMethod& meta = _metas[bestMeta]; + int returnTypeId = meta.returnType(); + QVector &qGenArgs = qGenArgsVectors[bestMeta]; + + // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's + // nothing in there for return values so these need to be explicitly runtime-registered! + Q_ASSERT(returnTypeId != QMetaType::UnknownType); + if (returnTypeId == QMetaType::UnknownType) { + isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + return; + } else if (returnTypeId == QMetaType::Void) { + bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], + qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); + if (!success) { + isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + } + return; + } else if (returnTypeId == scriptValueTypeId) { + ScriptValue result; + bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0], + qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], + qGenArgs[7], qGenArgs[8], qGenArgs[9]); + if (!success) { + isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + return; + } + V8ScriptValue v8Result = ScriptValueV8Wrapper::fullUnwrap(_engine, result); + arguments.GetReturnValue().Set(v8Result.get()); + return; + } else { + // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant + const char* typeName = meta.typeName(); + QVariant qRetVal(returnTypeId, static_cast(NULL)); + QGenericReturnArgument sRetVal(typeName, const_cast(qRetVal.constData())); + + bool success = + meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], + qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); + if (!success) { + isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + return; + } + V8ScriptValue v8Result = _engine->castVariantToValue(qRetVal); + arguments.GetReturnValue().Set(v8Result.get()); + return; + } + } + + // we failed to convert the call to C++, try to create a somewhat sane error message + if (parameterConversionFailureCount == 0) { + isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Native call of %1 failed: unexpected parameter count").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + return; + } + + const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId); + int methodNumArgs = meta.parameterCount(); + Q_ASSERT(methodNumArgs == numArgs); + + for (int arg = 0; arg < numArgs; ++arg) { + int methodArgTypeId = meta.parameterType(arg); + Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); + v8::Local argVal = arguments[arg]; + if (methodArgTypeId != scriptValueTypeId && methodArgTypeId != QMetaType::QVariant) { + QVariant varArgVal; + if (!_engine->castValueToVariant(V8ScriptValue(isolate, argVal), varArgVal, methodArgTypeId)) { + QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); + QByteArray argTypeName = _engine->valueType(V8ScriptValue(isolate, argVal)).toLatin1(); + isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") + .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName).toStdString().c_str()).ToLocalChecked()); + //context->throwError(V8ScriptContext::TypeError, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") + // .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName)); + return; + } + } + } + + isolate->ThrowError(v8::String::NewFromUtf8(isolate, QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now? + return; +} + +/*QVariant ScriptMethodV8Proxy::extension(Extension extension, const QVariant& argument) { + if (extension != Callable) return QVariant(); + V8ScriptContext* context = qvariant_cast(argument); + + QObject* qobject = _object; + if (!qobject) { + context->throwError(V8ScriptContext::ReferenceError, "Referencing deleted native object"); + return QVariant(); + } + + int scriptNumArgs = context->argumentCount(); + int numArgs = std::min(scriptNumArgs, _numMaxParms); + + const int scriptValueTypeId = qMetaTypeId(); + + int parameterConversionFailureId = 0; + int parameterConversionFailureCount = 0; + + int num_metas = _metas.size(); + QVector< QList > qScriptArgLists; + QVector< QVector > qGenArgsVectors; + QVector< QList > qVarArgLists; + qScriptArgLists.resize(num_metas); + qGenArgsVectors.resize(num_metas); + qVarArgLists.resize(num_metas); + bool isValidMetaSelected = false; + int bestMeta = 0; + int bestConversionPenaltyScore = 0; + + for (int i = 0; i < num_metas; i++) { + const QMetaMethod& meta = _metas[i]; + int methodNumArgs = meta.parameterCount(); + if (methodNumArgs != numArgs) { + continue; + } + + qGenArgsVectors[i].resize(10); + int conversionPenaltyScore = 0; + int conversionFailures = 0; + + for (int arg = 0; arg < numArgs; ++arg) { + int methodArgTypeId = meta.parameterType(arg); + Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); + V8ScriptValue argVal = context->argument(arg); + if (methodArgTypeId == scriptValueTypeId) { + qScriptArgLists[i].append(ScriptValue(new ScriptValueV8Wrapper(_engine, argVal))); + qGenArgsVectors[i][arg] = Q_ARG(ScriptValue, qScriptArgLists[i].back()); + } else if (methodArgTypeId == QMetaType::QVariant) { + qVarArgLists[i].append(argVal.toVariant()); + qGenArgsVectors[i][arg] = Q_ARG(QVariant, qVarArgLists[i].back()); + } else { + QVariant varArgVal; + if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) { + conversionFailures++; + } else { + qVarArgLists[i].append(varArgVal); + const QVariant& converted = qVarArgLists[i].back(); + conversionPenaltyScore = _engine->computeCastPenalty(argVal, methodArgTypeId); + + // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant + // A const_cast is needed because calling data() would detach the QVariant. + qGenArgsVectors[i][arg] = + QGenericArgument(QMetaType::typeName(converted.userType()), const_cast(converted.constData())); + } + } + } + if (conversionFailures) { + if (conversionFailures < parameterConversionFailureCount || !parameterConversionFailureCount) { + parameterConversionFailureCount = conversionFailures; + parameterConversionFailureId = meta.methodIndex(); + } + continue; + } + + if (!isValidMetaSelected) { + isValidMetaSelected = true; + bestMeta = i; + bestConversionPenaltyScore = conversionPenaltyScore; + } + if (isValidMetaSelected && bestConversionPenaltyScore > conversionPenaltyScore) { + bestMeta = i; + bestConversionPenaltyScore = conversionPenaltyScore; + } + } + + if (isValidMetaSelected) { + ScriptContextV8Wrapper ourContext(_engine, context); + ScriptContextGuard guard(&ourContext); + const QMetaMethod& meta = _metas[bestMeta]; + int returnTypeId = meta.returnType(); + QVector &qGenArgs = qGenArgsVectors[bestMeta]; + + // The Qt MOC engine will automatically call qRegisterMetaType on invokable parameters and properties, but there's + // nothing in there for return values so these need to be explicitly runtime-registered! + Q_ASSERT(returnTypeId != QMetaType::UnknownType); + if (returnTypeId == QMetaType::UnknownType) { + _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Cannot call native function %1, its return value has not been registered with Qt").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + return QVariant(); + } else if (returnTypeId == QMetaType::Void) { + bool success = meta.invoke(qobject, Qt::DirectConnection, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], + qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); + if (!success) { + _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + } + return QVariant(); + } else if (returnTypeId == scriptValueTypeId) { + ScriptValue result; + bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0], + qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], + qGenArgs[7], qGenArgs[8], qGenArgs[9]); + if (!success) { + _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + return QVariant(); + } + V8ScriptValue qResult = ScriptValueV8Wrapper::fullUnwrap(_engine, result); + return QVariant::fromValue(qResult); + } else { + // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant + const char* typeName = meta.typeName(); + QVariant qRetVal(returnTypeId, static_cast(NULL)); + QGenericReturnArgument sRetVal(typeName, const_cast(qRetVal.constData())); + + bool success = + meta.invoke(qobject, Qt::DirectConnection, sRetVal, qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], + qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); + if (!success) { + _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Unexpected: Native call of %1 failed").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + return QVariant(); + } + V8ScriptValue qResult = _engine->castVariantToValue(qRetVal); + return QVariant::fromValue(qResult); + } + } + + // we failed to convert the call to C++, try to create a somewhat sane error message + if (parameterConversionFailureCount == 0) { + _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Native call of %1 failed: unexpected parameter count").arg(fullName()).toStdString().c_str()).ToLocalChecked()); + return QVariant(); + } + + const QMetaMethod& meta = _object->metaObject()->method(parameterConversionFailureId); + int methodNumArgs = meta.parameterCount(); + Q_ASSERT(methodNumArgs == numArgs); + + for (int arg = 0; arg < numArgs; ++arg) { + int methodArgTypeId = meta.parameterType(arg); + Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); + V8ScriptValue argVal = context->argument(arg); + if (methodArgTypeId != scriptValueTypeId && methodArgTypeId != QMetaType::QVariant) { + QVariant varArgVal; + if (!_engine->castValueToVariant(argVal, varArgVal, methodArgTypeId)) { + QByteArray methodTypeName = QMetaType(methodArgTypeId).name(); + QByteArray argTypeName = _engine->valueType(argVal).toLatin1(); + _engine->getIsolate()->ThrowError(v8::String::NewFromUtf8(_engine->getIsolate(), QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") + .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName).toStdString().c_str()).ToLocalChecked()); + context->throwError(V8ScriptContext::TypeError, QString("Native call of %1 failed: Cannot convert parameter %2 from %3 to %4") + .arg(fullName()).arg(arg+1).arg(argTypeName, methodTypeName)); + return QVariant(); + } + } + } + + context->throwError(QString("Native call of %1 failed: could not locate an overload with the requested arguments").arg(fullName())); + Q_ASSERT(false); // really shouldn't have gotten here -- it didn't work before and it's working now? + return QVariant(); +}*/ + +QString ScriptSignalV8Proxy::fullName() const { + Q_ASSERT(_object); + if (!_object) return ""; + QString objectName = _object->objectName(); + if (!objectName.isEmpty()) { + return QString("%1.%2").arg(objectName, _meta.name()); + } + return QString("%1::%2").arg(_object->metaObject()->className(), _meta.name()); +} + +// Adapted from https://doc.qt.io/archives/qq/qq16-dynamicqobject.html, for connecting to a signal without a compile-time definition for it +int ScriptSignalV8Proxy::qt_metacall(QMetaObject::Call call, int id, void** arguments) { + id = ScriptSignalV8ProxyBase::qt_metacall(call, id, arguments); + if (id != 0 || call != QMetaObject::InvokeMetaMethod) { + return id; + } + + //V8ScriptValueList args(isolate, v8::Null(isolate)); + v8::Local args[Q_METAMETHOD_INVOKE_MAX_ARGS]; + int numArgs = _meta.parameterCount(); + for (int arg = 0; arg < numArgs; ++arg) { + int methodArgTypeId = _meta.parameterType(arg); + Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); + QVariant argValue(methodArgTypeId, arguments[arg+1]); + args[arg] = _engine->castVariantToValue(argValue).get(); + } + + QList connections; + withReadLock([&]{ + connections = _connections; + }); + + for (ConnectionList::iterator iter = connections.begin(); iter != connections.end(); ++iter) { + Connection& conn = *iter; + v8::Local callback = v8::Local::Cast(conn.callback.get()); + v8::Local v8This; + if (conn.thisValue.get()->IsNull()) { + v8This = _engine->getContext()->Global(); + } else { + v8This = conn.thisValue.get(); + } + //V8TODO: should there be a trycatch here? + callback->Call(_engine->getContext(), v8This, numArgs, args); + } + + return -1; +} + +int ScriptSignalV8Proxy::discoverMetaCallIdx() { + const QMetaObject* ourMeta = metaObject(); + return ourMeta->methodCount(); +} + +ScriptSignalV8Proxy::ConnectionList::iterator ScriptSignalV8Proxy::findConnection(V8ScriptValue thisObject, V8ScriptValue callback) { + auto iterOut = resultWithReadLock([&]{ + ConnectionList::iterator iter; + for (iter = _connections.begin(); iter != _connections.end(); ++iter) { + Connection& conn = *iter; + if (conn.callback.constGet()->StrictEquals(callback.constGet()) && conn.thisValue.constGet()->StrictEquals(thisObject.constGet())) { + break; + } + } + return iter; + }); + return iterOut; +} + + +void ScriptSignalV8Proxy::connect(V8ScriptValue arg0, V8ScriptValue arg1) { + QObject* qobject = _object; + if (!qobject) { + _engine->getIsolate()->ThrowError("Referencing deleted native object"); + return; + } + + v8::Isolate *isolate = _engine->getIsolate(); + // untangle the arguments + V8ScriptValue callback(isolate, v8::Null(isolate)); + V8ScriptValue callbackThis(isolate, v8::Null(isolate)); + if (arg1.get()->IsFunction()) { + callbackThis = arg0; + callback = arg1; + } else { + callback = arg0; + } + if (!callback.get()->IsFunction()) { + _engine->getIsolate()->ThrowError("Function expected as argument to 'connect'"); + return; + } + + // are we already connected? + { + ConnectionList::iterator lookup = findConnection(callbackThis, callback); + if (lookup != _connections.end()) { + return; // already exists + } + } + + // add a reference to ourselves to the destination callback + v8::Local destFunction = v8::Local::Cast(callback.get()); + v8::Local destDataName = v8::String::NewFromUtf8(isolate, "__data__").ToLocalChecked(); + v8::Local destData; + auto destFunctionContext = destFunction->CreationContext(); + V8ScriptValue v8ThisObject = ScriptValueV8Wrapper::fullUnwrap(_engine, thisObject()); + //Q_ASSERT(destFunction->InternalFieldCount() == 4); + //Q_ASSERT(destData.get()->IsArray()); + //v8::Local destData = destFunction->GetInternalField(3); + if (destFunction->Get(destFunctionContext, destDataName).ToLocal(&destData) && destData->IsArray()) { + v8::Local destArray = v8::Local::Cast(destData); + int length = destArray->Length();//destData.property("length").toInteger(); + v8::Local newArray = v8::Array::New(isolate, length + 1); + bool foundIt = false; + for (int idx = 0; idx < length && !foundIt; ++idx) { + v8::Local entry = destArray->Get(destFunctionContext, idx).ToLocalChecked(); + newArray->Set(destFunctionContext, idx, entry); + } + newArray->Set(destFunctionContext, length, v8ThisObject.get()); + destFunction->Set(destFunctionContext, destDataName, newArray); + } else { + v8::Local newArray = v8::Array::New(isolate, 1); + newArray->Set(destFunctionContext, 0, v8ThisObject.get()); + destFunction->Set(destFunctionContext, destDataName, newArray); + } + /*{ + V8ScriptValueList args; + args << thisObject(); + destData.property("push").call(destData, args); + }*/ + + // add this to our internal list of connections + Connection newConnection(callbackThis, callback); + //newConn.callback = callback; + //newConn.thisValue = callbackThis; + + withWriteLock([&]{ + _connections.append(newConnection); + }); + + // inform Qt that we're connecting to this signal + if (!_isConnected) { + auto result = QMetaObject::connect(qobject, _meta.methodIndex(), this, _metaCallId); + Q_ASSERT(result); + _isConnected = true; + } +} + +void ScriptSignalV8Proxy::disconnect(V8ScriptValue arg0, V8ScriptValue arg1) { + QObject* qobject = _object; + if (!qobject) { + _engine->getIsolate()->ThrowError("Referencing deleted native object"); + return; + } + v8::Isolate *isolate = _engine->getIsolate(); + + // untangle the arguments + V8ScriptValue callback(isolate, v8::Null(isolate)); + V8ScriptValue callbackThis(isolate, v8::Null(isolate)); + if (arg1.get()->IsFunction()) { + callbackThis = arg0; + callback = arg1; + } else { + callback = arg0; + } + if (!callback.get()->IsFunction()) { + _engine->getIsolate()->ThrowError("Function expected as argument to 'disconnect'"); + return; + } + + // locate this connection in our list of connections + { + ConnectionList::iterator lookup = findConnection(callbackThis, callback); + if (lookup == _connections.end()) { + return; // not here + } + + // remove it from our internal list of connections + withWriteLock([&]{ + _connections.erase(lookup); + }); + } + + // remove a reference to ourselves from the destination callback + v8::Local destFunction = v8::Local::Cast(callback.get()); + v8::Local destDataName = v8::String::NewFromUtf8(isolate, "__data__").ToLocalChecked(); + v8::Local destData; + auto destFunctionContext = destFunction->CreationContext(); + V8ScriptValue v8ThisObject = ScriptValueV8Wrapper::fullUnwrap(_engine, thisObject()); + //V8ScriptValue destData = callback.data(); + //Q_ASSERT(destData->IsArray()); + if (destFunction->Get(destFunctionContext, destDataName).ToLocal(&destData) && destData->IsArray()) { + v8::Local destArray = v8::Local::Cast(destData); + int length = destArray->Length();//destData.property("length").toInteger(); + v8::Local newArray = v8::Array::New(isolate, length - 1); + bool foundIt = false; + int newIndex = 0; + for (int idx = 0; idx < length && !foundIt; ++idx) { + v8::Local entry = destArray->Get(destFunctionContext, idx).ToLocalChecked(); + if (entry->StrictEquals(v8ThisObject.get())) { + foundIt = true; + //V8ScriptValueList args; + //args << idx << 1; + //destData.property("splice").call(destData, args); + } else { + newArray->Set(destFunctionContext, newIndex, entry); + newIndex++; + } + } + Q_ASSERT(foundIt); + destFunction->Set(destFunctionContext, destDataName, newArray); + } else { + Q_ASSERT(false); + } + + // inform Qt that we're no longer connected to this signal + if (_connections.empty()) { + Q_ASSERT(_isConnected); + bool result = QMetaObject::disconnect(qobject, _meta.methodIndex(), this, _metaCallId); + Q_ASSERT(result); + _isConnected = false; + } +} diff --git a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.h b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.h new file mode 100644 index 0000000000..22bd9618bd --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.h @@ -0,0 +1,245 @@ +// +// ScriptObjectV8Proxy.h +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 12/5/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptObjectV8Proxy_h +#define hifi_ScriptObjectV8Proxy_h + +#include +#include +#include +#include + +#include "../ScriptEngine.h" +#include "../Scriptable.h" +#include "ScriptEngineV8.h" + +#include + +class ScriptEngineV8; +class ScriptSignalV8Proxy; + +/// [V8] (re-)implements the translation layer between ScriptValue and QObject. This object +/// will focus exclusively on property get/set until function calls appear to be a problem +class ScriptObjectV8Proxy final { +private: // implementation + class PropertyDef { + public: + PropertyDef(v8::Isolate *isolate, v8::Local string) : name(isolate, string) {}; + V8ScriptString name; + ScriptValue::PropertyFlags flags; + }; + class MethodDef { + public: + MethodDef(v8::Isolate *isolate, v8::Local string) : name(isolate, string) {}; + V8ScriptString name; + int numMaxParms; + QList methods; + }; + class SignalDef { + public: + SignalDef(v8::Isolate *isolate, v8::Local string) : name(isolate, string) {}; + V8ScriptString name; + QMetaMethod signal; + }; + using PropertyDefMap = QHash; + using MethodDefMap = QHash; + using SignalDefMap = QHash; + using InstanceMap = QHash >; + + static constexpr uint PROPERTY_TYPE = 0x1000; + static constexpr uint METHOD_TYPE = 0x2000; + static constexpr uint SIGNAL_TYPE = 0x3000; + static constexpr uint TYPE_MASK = 0xF000; + +public: // construction + ScriptObjectV8Proxy(ScriptEngineV8* engine, QObject* object, bool ownsObject, const ScriptEngine::QObjectWrapOptions& options); + virtual ~ScriptObjectV8Proxy(); + + static V8ScriptValue newQObject(ScriptEngineV8* engine, + QObject* object, + ScriptEngine::ValueOwnership ownership = ScriptEngine::QtOwnership, + const ScriptEngine::QObjectWrapOptions& options = ScriptEngine::QObjectWrapOptions()); + static ScriptObjectV8Proxy* unwrapProxy(const V8ScriptValue& val); + static QObject* unwrap(const V8ScriptValue& val); + inline QObject* toQObject() const { return _object; } + inline v8::Local toV8Value() const { + v8::EscapableHandleScope handleScope(_engine->getIsolate()); + return handleScope.Escape(_v8Object.Get(_engine->getIsolate())); + } + +public: + enum QueryFlag + { + HandlesReadAccess = 0x00000001, + HandlesWriteAccess = 0x00000002, + }; + Q_DECLARE_FLAGS(QueryFlags, QueryFlag); + + virtual QString name() const; + + virtual V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id); + virtual ScriptValue::PropertyFlags propertyFlags(const V8ScriptValue& object, const V8ScriptString& name, uint id); + //V8TODO + virtual QueryFlags queryProperty(const V8ScriptValue& object, const V8ScriptString& name, QueryFlags flags, uint* id); + virtual void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value); + static void v8Get(v8::Local name, const v8::PropertyCallbackInfo& info); + static void v8Set(v8::Local name, v8::Local value_obj, const v8::PropertyCallbackInfo& info); + +private: // implementation + void investigate(); + +private: // storage + ScriptEngineV8* _engine; + const ScriptEngine::QObjectWrapOptions _wrapOptions; + PropertyDefMap _props; + MethodDefMap _methods; + SignalDefMap _signals; + InstanceMap _signalInstances; + const bool _ownsObject; + QPointer _object; + // V8TODO Is this necessary? + v8::UniquePersistent _v8ObjectTemplate; + // V8TODO Maybe it doesn't really need to point to itsef? + v8::UniquePersistent _v8Object; + + Q_DISABLE_COPY(ScriptObjectV8Proxy) +}; + +/// [V8] (re-)implements the translation layer between ScriptValue and QVariant where a prototype is set. +/// This object depends on a ScriptObjectV8Proxy to provide the prototype's behavior +class ScriptVariantV8Proxy final { +public: // construction + ScriptVariantV8Proxy(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue scriptProto, ScriptObjectV8Proxy* proto); + + static V8ScriptValue newVariant(ScriptEngineV8* engine, const QVariant& variant, V8ScriptValue proto); + static ScriptVariantV8Proxy* unwrapProxy(const V8ScriptValue& val); + static QVariant unwrap(const V8ScriptValue& val); + inline QVariant toQVariant() const { return _variant; } + //inline QVariant toV8Value() const { return _variant; } + inline v8::Local toV8Value() const { + v8::EscapableHandleScope handleScope(_engine->getIsolate()); + return handleScope.Escape(_v8Object.Get(_engine->getIsolate())); + } + +public: // QScriptClass implementation + virtual QString name() const { return _name; } + + virtual V8ScriptValue prototype() const { return _scriptProto; } + + virtual V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { + return _proto->property(object, name, id); + } + virtual ScriptValue::PropertyFlags propertyFlags(const V8ScriptValue& object, const V8ScriptString& name, uint id) { + return _proto->propertyFlags(object, name, id); + } + /*virtual QueryFlags queryProperty(const V8ScriptValue& object, const V8ScriptString& name, QueryFlags flags, uint* id) { + return _proto->queryProperty(object, name, flags, id); + }*/ + virtual void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) { + return _proto->setProperty(object, name, id, value); + } + +private: // storage + ScriptEngineV8* _engine; + QVariant _variant; + V8ScriptValue _scriptProto; + ScriptObjectV8Proxy* _proto; + QString _name; + v8::UniquePersistent _v8ObjectTemplate; + v8::UniquePersistent _v8Object; + + Q_DISABLE_COPY(ScriptVariantV8Proxy) +}; + +class ScriptMethodV8Proxy final { +public: // construction + ScriptMethodV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, + const QList& metas, int numMaxParms); + +public: // QScriptClass implementation + virtual QString name() const { return fullName(); } + //virtual bool supportsExtension(Extension extension) const; + static void callback(const v8::FunctionCallbackInfo& arguments); + void call(const v8::FunctionCallbackInfo& arguments); + //virtual QVariant extension(Extension extension, const QVariant& argument = QVariant()); + static V8ScriptValue newMethod(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, + const QList& metas, int numMaxParams); + +private: + QString fullName() const; + +private: // storage + const int _numMaxParms; + ScriptEngineV8* _engine; + QPointer _object; + V8ScriptValue _objectLifetime; + const QList _metas; + + Q_DISABLE_COPY(ScriptMethodV8Proxy) +}; + +// This abstract base class serves solely to declare the Q_INVOKABLE methods for ScriptSignalV8Proxy +// as we're overriding qt_metacall later for the signal callback yet still want to support +// metacalls for the connect/disconnect API +class ScriptSignalV8ProxyBase : public QObject, protected Scriptable { + Q_OBJECT +public: // API + // arg1 was had Null default value, but that needs isolate pointer in V8 + Q_INVOKABLE virtual void connect(V8ScriptValue arg0, V8ScriptValue arg1) = 0; + Q_INVOKABLE virtual void disconnect(V8ScriptValue arg0, V8ScriptValue arg1) = 0; +}; + +class ScriptSignalV8Proxy final : public ScriptSignalV8ProxyBase, public ReadWriteLockable { +private: // storage + class Connection { + public: + V8ScriptValue thisValue; + V8ScriptValue callback; + Connection(const V8ScriptValue &v8ThisValue, const V8ScriptValue &v8Callback) : + thisValue(v8ThisValue), callback(v8Callback) {}; + }; + using ConnectionList = QList; + +public: // construction + inline ScriptSignalV8Proxy(ScriptEngineV8* engine, QObject* object, V8ScriptValue lifetime, const QMetaMethod& meta) : + _engine(engine), _object(object), _objectLifetime(lifetime), _meta(meta), _metaCallId(discoverMetaCallIdx()) {} + +private: // implementation + virtual int qt_metacall(QMetaObject::Call call, int id, void** arguments) override; + int discoverMetaCallIdx(); + ConnectionList::iterator findConnection(V8ScriptValue thisObject, V8ScriptValue callback); + QString fullName() const; + +public: // API + // arg1 was had Null default value, but that needs isolate pointer to cerate Null in V8 + virtual void connect(V8ScriptValue arg0, V8ScriptValue arg1) override; + virtual void disconnect(V8ScriptValue arg0, V8ScriptValue arg1) override; + +private: // storage + ScriptEngineV8* _engine; + QPointer _object; + V8ScriptValue _objectLifetime; + const QMetaMethod _meta; + const int _metaCallId; + ConnectionList _connections; + bool _isConnected{ false }; + + Q_DISABLE_COPY(ScriptSignalV8Proxy) +}; + +#endif // hifi_ScriptObjectV8Proxy_h + +/// @} diff --git a/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.cpp b/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.cpp new file mode 100644 index 0000000000..edf7f033e2 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.cpp @@ -0,0 +1,64 @@ +// +// ScriptProgramV8Wrapper.cpp +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 8/24/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptProgramV8Wrapper.h" + +#include "ScriptEngineV8.h" +#include "ScriptValueV8Wrapper.h" + +ScriptProgramV8Wrapper* ScriptProgramV8Wrapper::unwrap(ScriptProgramPointer val) { + if (!val) { + return nullptr; + } + + return dynamic_cast(val.get()); +} + +ScriptSyntaxCheckResultPointer ScriptProgramV8Wrapper::checkSyntax() { + if (!_isCompiled) { + compile(); + } + return std::make_shared(_compileResult); +} + +bool ScriptProgramV8Wrapper::compile() { + int errorColumnNumber = 0; + int errorLineNumber = 0; + QString errorMessage = ""; + QString errorBacktrace = ""; + ScriptSyntaxCheckResult::State state; + v8::TryCatch tryCatch(_engine->getIsolate()); + v8::ScriptOrigin scriptOrigin(_engine->getIsolate(), v8::String::NewFromUtf8(_engine->getIsolate(), _url.toStdString().c_str()).ToLocalChecked()); + v8::Local script; + if (v8::Script::Compile(_engine->getContext(), v8::String::NewFromUtf8(_engine->getIsolate(), _source.toStdString().c_str()).ToLocalChecked(), &scriptOrigin).ToLocal(&script)) { + _compileResult = ScriptSyntaxCheckResultV8Wrapper(ScriptSyntaxCheckResult::Valid); + _value = V8ScriptProgram(_engine->getIsolate(), script); + return true; + } + v8::String::Utf8Value utf8Value(_engine->getIsolate(), tryCatch.Exception()); + errorMessage = QString(*utf8Value); + v8::Local exceptionMessage = tryCatch.Message(); + if (!exceptionMessage.IsEmpty()) { + errorLineNumber = exceptionMessage->GetLineNumber(_engine->getContext()).FromJust(); + errorColumnNumber = exceptionMessage->GetStartColumn(_engine->getContext()).FromJust(); + v8::Local backtraceV8String; + if (tryCatch.StackTrace(_engine->getContext()).ToLocal(&backtraceV8String) && backtraceV8String->IsString() && + v8::Local::Cast(backtraceV8String)->Length() > 0) { + v8::String::Utf8Value backtraceUtf8Value(_engine->getIsolate(), backtraceV8String); + errorBacktrace = *backtraceUtf8Value; + } + } + //V8TODO + _compileResult = ScriptSyntaxCheckResultV8Wrapper(ScriptSyntaxCheckResult::Error, errorColumnNumber, errorLineNumber, errorMessage, errorBacktrace); + return false; +} diff --git a/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.h b/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.h new file mode 100644 index 0000000000..03313292b6 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptProgramV8Wrapper.h @@ -0,0 +1,76 @@ +// +// ScriptProgramV8Wrapper.h +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 5/21/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptProgramV8Wrapper_h +#define hifi_ScriptProgramV8Wrapper_h + +#include + +#include "../ScriptProgram.h" +#include "ScriptEngineV8.h" + +class ScriptSyntaxCheckResultV8Wrapper final : public ScriptSyntaxCheckResult { +public: // construction + inline ScriptSyntaxCheckResultV8Wrapper() : _errorColumnNumber(0), _errorLineNumber(0), _errorMessage("Not compiled"), _state(ScriptSyntaxCheckResult::Error) {} + inline ScriptSyntaxCheckResultV8Wrapper(State state, int columnNumber = 0, int lineNumber = 0, const QString &message = QString(""), const QString &errorBacktrace = QString("")) : + _errorColumnNumber(columnNumber), _errorLineNumber(lineNumber), _errorMessage(message), _state(state) {} + +public: // ScriptSyntaxCheckResult implementation + virtual int errorColumnNumber() const override {return _errorColumnNumber;} + virtual int errorLineNumber() const override {return _errorLineNumber;} + virtual QString errorMessage() const override {return _errorMessage;} + virtual QString errorBacktrace() const override {return _errorBacktrace;} + virtual State state() const override {return _state;} + +private: // storage + int _errorColumnNumber; + int _errorLineNumber; + QString _errorMessage; + QString _errorBacktrace; + State _state; +}; + +/// [V8] Implements ScriptProgram for V8 and translates calls for V8ScriptProgram +class ScriptProgramV8Wrapper final : public ScriptProgram { +public: // construction + /*inline ScriptProgramV8Wrapper(ScriptEngineV8* engine, const V8ScriptProgram& value) : + _engine(engine), _value(value) {}*/ + //inline ScriptProgramV8Wrapper(ScriptEngineV8* engine, V8ScriptProgram&& value) : + // _engine(engine), _value(std::move(value)) {} + inline ScriptProgramV8Wrapper(ScriptEngineV8* engine, QString source, QString url) : + _engine(engine), _source(source), _url(url), _value(engine->getIsolate(), v8::Local()) {} + static ScriptProgramV8Wrapper* unwrap(ScriptProgramPointer val); + bool compile(); + inline const V8ScriptProgram& toV8Value() const { return _value; } + +public: // ScriptProgram implementation + virtual ScriptSyntaxCheckResultPointer checkSyntax() override; + virtual QString fileName() const override {return _url;} + virtual QString sourceCode() const override {return _source;} + +private: // storage + ScriptEngineV8 *_engine; + QString _source; + QString _url; + V8ScriptProgram _value; + bool _isCompiled = false; + ScriptSyntaxCheckResultV8Wrapper _compileResult; + //V8TODO: how to make program run on multiple isolates? +}; + +#endif // hifi_ScriptValueV8Wrapper_h + +/// @} diff --git a/libraries/script-engine/src/v8/ScriptValueIteratorV8Wrapper.cpp b/libraries/script-engine/src/v8/ScriptValueIteratorV8Wrapper.cpp new file mode 100644 index 0000000000..317a5734eb --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptValueIteratorV8Wrapper.cpp @@ -0,0 +1,82 @@ +// +// ScriptValueIteratorV8Wrapper.cpp +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 8/29/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptValueIteratorV8Wrapper.h" + +V8ScriptValueIterator::V8ScriptValueIterator(ScriptEngineV8* engine, v8::Local object) : _engine(engine) { + _context.Reset(_engine->getIsolate(), _engine->getContext()); + auto context = _context.Get(_engine->getIsolate()); + v8::Local v8Object; + if (!object->ToObject(context).ToLocal(&v8Object)) { + Q_ASSERT(false); + } + _object.Reset(_engine->getIsolate(), v8Object); + _propertyNames.Reset(_engine->getIsolate(), v8Object->GetOwnPropertyNames(context).ToLocalChecked()); + _length = _propertyNames.Get(_engine->getIsolate())->Length(); + _currentIndex = 0; +} + +bool V8ScriptValueIterator::hasNext() const { + return _currentIndex < _length - 1; +} + +QString V8ScriptValueIterator::name() const { + auto context = _context.Get(_engine->getIsolate()); + v8::Local propertyName; + if (!_propertyNames.Get(_engine->getIsolate())->Get(context, _length).ToLocal(&propertyName)) { + Q_ASSERT(false); + } + return QString(*v8::String::Utf8Value(_engine->getIsolate(), propertyName)); +} + +void V8ScriptValueIterator::next() { + if (_length < _currentIndex - 1) { + _length++; + } +} + +V8ScriptValue V8ScriptValueIterator::value() { + auto context = _context.Get(_engine->getIsolate()); + v8::Local v8Value; + v8::Local propertyName; + if (!_propertyNames.Get(_engine->getIsolate())->Get(context, _length).ToLocal(&propertyName)) { + Q_ASSERT(false); + } + if (!_object.Get(_engine->getIsolate())->Get(context, propertyName->ToString(context).ToLocalChecked()).ToLocal(&v8Value)) { + Q_ASSERT(false); + } + return V8ScriptValue(_engine->getIsolate(), v8Value); +} + +ScriptValue::PropertyFlags ScriptValueIteratorV8Wrapper::flags() const { + //V8TODO + //return (ScriptValue::PropertyFlags)(int)_value.flags(); + return (ScriptValue::PropertyFlags)(0); +} + +bool ScriptValueIteratorV8Wrapper::hasNext() const { + return _value->hasNext(); +} + +QString ScriptValueIteratorV8Wrapper::name() const { + return _value->name(); +} + +void ScriptValueIteratorV8Wrapper::next() { + _value->next(); +} + +ScriptValue ScriptValueIteratorV8Wrapper::value() const { + V8ScriptValue result = _value->value(); + return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); +} diff --git a/libraries/script-engine/src/v8/ScriptValueIteratorV8Wrapper.h b/libraries/script-engine/src/v8/ScriptValueIteratorV8Wrapper.h new file mode 100644 index 0000000000..5ce7a8f4c4 --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptValueIteratorV8Wrapper.h @@ -0,0 +1,64 @@ +// +// ScriptValueIteratorV8Wrapper.h +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 8/29/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_ScriptValueIteratorV8Wrapper_h +#define hifi_ScriptValueIteratorV8Wrapper_h + +#include + +#include "../ScriptValueIterator.h" +#include "ScriptEngineV8.h" +#include "ScriptValueV8Wrapper.h" + +class V8ScriptValueIterator { +public: + V8ScriptValueIterator(ScriptEngineV8* engine, v8::Local object); + bool hasNext() const; + QString name() const; + void next(); + V8ScriptValue value(); +private: + v8::UniquePersistent _propertyNames; + v8::UniquePersistent _object; + v8::UniquePersistent _context; + int _length; + int _currentIndex; + ScriptEngineV8 *_engine; +}; + +/// [V8] Implements ScriptValueIterator for V8 and translates calls for V8ScriptValueIterator +class ScriptValueIteratorV8Wrapper final : public ScriptValueIterator { +public: // construction + inline ScriptValueIteratorV8Wrapper(ScriptEngineV8* engine, const ScriptValue& object) : + _engine(engine), _value(new V8ScriptValueIterator(engine, ScriptValueV8Wrapper::fullUnwrap(engine, object).get())) {} + inline ScriptValueIteratorV8Wrapper(ScriptEngineV8* engine, const V8ScriptValue& object) : + _engine(engine), _value(new V8ScriptValueIterator(engine, object.constGet())) {} + +public: // ScriptValueIterator implementation + virtual ScriptValue::PropertyFlags flags() const override; + virtual bool hasNext() const override; + virtual QString name() const override; + virtual void next() override; + virtual ScriptValue value() const override; + +private: // storage + ScriptEngineV8 *_engine; + std::shared_ptr _value; +}; + +#endif // hifi_ScriptValueIteratorV8Wrapper_h + +/// @} diff --git a/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp b/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp new file mode 100644 index 0000000000..397b16ea2e --- /dev/null +++ b/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp @@ -0,0 +1,367 @@ +// +// ScriptValueV8Wrapper.cpp +// libraries/script-engine/src/v8 +// +// Created by Heather Anderson on 5/16/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2021 Vircadia contributors. +// Copyright 2022 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ScriptValueV8Wrapper.h" + +#include "ScriptValueIteratorV8Wrapper.h" + +#include "../ScriptEngineLogging.h" + + +void ScriptValueV8Wrapper::release() { + delete this; +} + +ScriptValueProxy* ScriptValueV8Wrapper::copy() const { + //V8TODO: check if the value needs to be copied or just wrapper + return new ScriptValueV8Wrapper(_engine, _value); +} + +ScriptValueV8Wrapper* ScriptValueV8Wrapper::unwrap(const ScriptValue& val) { + return dynamic_cast(val.ptr()); +} + +V8ScriptValue ScriptValueV8Wrapper::fullUnwrap(const ScriptValue& value) const { + ScriptValueV8Wrapper* unwrapped = unwrap(value); + if (unwrapped) { + if (unwrapped->engine().get() != _engine) { + //return _engine->toScriptValue(unwrapped->toVariant()); + return _engine->castVariantToValue(unwrapped->toVariant()); + } else { + return unwrapped->toV8Value(); + } + } + QVariant varValue = value.toVariant(); + return _engine->castVariantToValue(varValue); +} + +V8ScriptValue ScriptValueV8Wrapper::fullUnwrap(ScriptEngineV8* engine, const ScriptValue& value) { + ScriptValueV8Wrapper* unwrapped = unwrap(value); + if (unwrapped) { + if (unwrapped->engine().get() != engine) { + //return static_cast(engine)->toScriptValue(unwrapped->toVariant()); + return engine->castVariantToValue(unwrapped->toVariant()); + } else { + return unwrapped->toV8Value(); + } + } + QVariant varValue = value.toVariant(); + return engine->castVariantToValue(varValue); +} + +ScriptValue ScriptValueV8Wrapper::call(const ScriptValue& thisObject, const ScriptValueList& args) { + V8ScriptValue v8This = fullUnwrap(thisObject); + //V8ScriptValueList qArgs; + Q_ASSERT(args.length() <= Q_METAMETHOD_INVOKE_MAX_ARGS); + //V8TODO I'm not sure how else to do this since v8::Local should probably be on stack, not heap + v8::Local v8Args[Q_METAMETHOD_INVOKE_MAX_ARGS]; + int argIndex = 0; + for (ScriptValueList::const_iterator iter = args.begin(); iter != args.end(); ++iter) { + v8Args[argIndex++] = fullUnwrap(*iter).get(); + } + //V8TODO should there be a v8 try-catch here? + // IsFunction check should be here probably + v8::Local v8Function = v8::Local::Cast(_value.get()); + auto maybeResult = v8Function->Call(_engine->getContext(), v8This.get(), args.length(), v8Args); + v8::Local result; + if (maybeResult.ToLocal(&result)) { + return ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine->getIsolate(), result))); + } else { + //V8TODO Add more details + qWarning("JS function call failed"); + return _engine->undefinedValue(); + } +} + +ScriptValue ScriptValueV8Wrapper::call(const ScriptValue& thisObject, const ScriptValue& arguments) { + V8ScriptValue v8This = fullUnwrap(thisObject); + V8ScriptValue v8Args = fullUnwrap(arguments); + // V8TODO should there be a v8 try-catch here? + // IsFunction check should be here probably + // V8TODO I'm not sure in what format arguments are yet, backtrace will show how it is used + Q_ASSERT(false); + return _engine->undefinedValue(); + /*v8::Local v8Function = v8::Local::Cast(_value.get()); + auto maybeResult = v8Function->Call(_engine->getContext(), v8This, v8Args); + v8::Local result; + if (maybeResult.ToLocal(&result)) { + return ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine->getContext(), result))); + } else { + //V8TODO Add more details + qWarning("JS function call failed"); + return _engine->undefinedValue(); + }*/ +} + +ScriptValue ScriptValueV8Wrapper::construct(const ScriptValueList& args) { + Q_ASSERT(args.length() <= Q_METAMETHOD_INVOKE_MAX_ARGS); + //V8TODO I'm not sure how else to do this since v8::Local should probably be on stack, not heap + v8::Local v8Args[Q_METAMETHOD_INVOKE_MAX_ARGS]; + int argIndex = 0; + for (ScriptValueList::const_iterator iter = args.begin(); iter != args.end(); ++iter) { + v8Args[argIndex++] = fullUnwrap(*iter).get(); + } + //V8TODO should there be a v8 try-catch here? + // IsFunction check should be here probably + v8::Local v8Function = v8::Local::Cast(_value.get()); + auto maybeResult = v8Function->NewInstance(_engine->getContext(), args.length(), v8Args); + v8::Local result; + if (maybeResult.ToLocal(&result)) { + return ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine->getIsolate(), result))); + } else { + //V8TODO Add more details + qWarning("JS function call failed"); + return _engine->undefinedValue(); + } +} + +ScriptValue ScriptValueV8Wrapper::construct(const ScriptValue& arguments) { + // V8TODO I'm not sure in what format arguments are yet, backtrace will show how it is used + Q_ASSERT(false); + return _engine->undefinedValue(); + //V8ScriptValue unwrapped = fullUnwrap(arguments); + //V8ScriptValue result = _value.construct(unwrapped); + //return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); +} + +ScriptValue ScriptValueV8Wrapper::data() const { + //V8TODO I'm not sure how this would work in V8 + //V8ScriptValue result = _value.data(); + //return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); + return _engine->nullValue(); +} + +ScriptEnginePointer ScriptValueV8Wrapper::engine() const { + if (!_engine) { + return ScriptEnginePointer(); + } + return _engine->shared_from_this(); +} + +ScriptValueIteratorPointer ScriptValueV8Wrapper::newIterator() const { + return std::make_shared(_engine, _value); +} + +ScriptValue ScriptValueV8Wrapper::property(const QString& name, const ScriptValue::ResolveFlags& mode) const { + if (!_value.constGet()->IsObject()) { + //V8TODO: what about flags? + v8::Local resultLocal; + v8::Local key = v8::String::NewFromUtf8(_engine->getIsolate(), name.toStdString().c_str(),v8::NewStringType::kNormal).ToLocalChecked(); + if (_value.constGet()->ToObject(_value.constGetContext()).ToLocalChecked() + ->Get(_value.constGetContext(), key).ToLocal(&resultLocal)) { + V8ScriptValue result(_engine->getIsolate(), resultLocal); + return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); + } + } + v8::Local nullValue = v8::Null(_engine->getIsolate()); + V8ScriptValue nullScriptValue(_engine->getIsolate(), std::move(nullValue)); + return ScriptValue(new ScriptValueV8Wrapper(_engine, nullScriptValue)); + //V8ScriptValue result = _value.property(name, (V8ScriptValue::ResolveFlags)(int)mode); + //return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); +} + +ScriptValue ScriptValueV8Wrapper::property(quint32 arrayIndex, const ScriptValue::ResolveFlags& mode) const { + if (!_value.constGet()->IsObject()) { + //V8TODO: what about flags? + v8::Local resultLocal; + if (_value.constGet()->ToObject(_value.constGetContext()).ToLocalChecked() + ->Get(_value.constGetContext(), arrayIndex).ToLocal(&resultLocal)) { + V8ScriptValue result(_engine->getIsolate(), resultLocal); + return ScriptValue(new ScriptValueV8Wrapper(_engine, std::move(result))); + } + } + v8::Local nullValue = v8::Null(_engine->getIsolate()); + V8ScriptValue nullScriptValue(_engine->getIsolate(), std::move(nullValue)); + return ScriptValue(new ScriptValueV8Wrapper(_engine, nullScriptValue)); +} + +void ScriptValueV8Wrapper::setData(const ScriptValue& value) { + V8ScriptValue unwrapped = fullUnwrap(value); + (**_value.get()) = (**unwrapped.get()); +} + +void ScriptValueV8Wrapper::setProperty(const QString& name, const ScriptValue& value, const ScriptValue::PropertyFlags& flags) { + V8ScriptValue unwrapped = fullUnwrap(value); + if(_value.constGet()->IsObject()) { + v8::Local key = v8::String::NewFromUtf8(_engine->getIsolate(), name.toStdString().c_str(),v8::NewStringType::kNormal).ToLocalChecked(); + v8::Maybe retVal = _value.get()->ToObject(_value.getContext()).ToLocalChecked() + ->Set(_value.getContext(), key, unwrapped.constGet()); + if (retVal.IsJust() ? !retVal.FromJust() : true){ + qDebug(scriptengine) << "Failed to set property"; + } + } else { + qDebug(scriptengine) << "Failed to set property - parent is not an object"; + } + //V8TODO: what about flags? + //_value.setProperty(name, unwrapped, (V8ScriptValue::PropertyFlags)(int)flags); +} + +void ScriptValueV8Wrapper::setProperty(quint32 arrayIndex, const ScriptValue& value, const ScriptValue::PropertyFlags& flags) { + V8ScriptValue unwrapped = fullUnwrap(value); + if(_value.constGet()->IsObject()) { + v8::Maybe retVal(_value.get()->ToObject(_value.getContext()).ToLocalChecked() + ->Set(_value.getContext(), arrayIndex, unwrapped.constGet())); + if (retVal.IsJust() ? !retVal.FromJust() : true){ + qDebug(scriptengine) << "Failed to set property"; + } + } else { + qDebug(scriptengine) << "Failed to set property - parent is not an object"; + } + //V8TODO: what about flags? + //_value.setProperty(arrayIndex, unwrapped, (V8ScriptValue::PropertyFlags)(int)flags); +} + +void ScriptValueV8Wrapper::setPrototype(const ScriptValue& prototype) { + ScriptValueV8Wrapper* unwrappedPrototype = unwrap(prototype); + if (unwrappedPrototype) { + if(unwrappedPrototype->toV8Value().constGet()->IsObject() && _value.constGet()->IsObject()) { + v8::Maybe retVal = _value.get()->ToObject(_value.getContext()).ToLocalChecked() + ->SetPrototype(_value.getContext(), unwrappedPrototype->toV8Value().constGet()); + if (retVal.IsJust() ? !retVal.FromJust() : true){ + qDebug(scriptengine) << "Failed to assign prototype"; + } + } else { + qDebug(scriptengine) << "Failed to assign prototype - one of values is not an object"; + } + } +} + +bool ScriptValueV8Wrapper::strictlyEquals(const ScriptValue& other) const { + ScriptValueV8Wrapper* unwrappedOther = unwrap(other); + return unwrappedOther ? _value.constGet()->StrictEquals(unwrappedOther->toV8Value().constGet()) : false; +} + +bool ScriptValueV8Wrapper::toBool() const { + return _value.constGet()->ToBoolean(_engine->getIsolate())->Value(); +} + +qint32 ScriptValueV8Wrapper::toInt32() const { + v8::Local *integer; + Q_ASSERT(_value.constGet()->ToInteger(_value.constGetContext()).ToLocal(integer)); + return static_cast((*integer)->Value()); +} + +double ScriptValueV8Wrapper::toInteger() const { + v8::Local *integer; + Q_ASSERT(_value.constGet()->ToInteger(_value.constGetContext()).ToLocal(integer)); + return (*integer)->Value(); +} + +double ScriptValueV8Wrapper::toNumber() const { + v8::Local *number; + Q_ASSERT(_value.constGet()->ToNumber(_value.constGetContext()).ToLocal(number)); + return (*number)->Value(); +} + +QString ScriptValueV8Wrapper::toString() const { + v8::String::Utf8Value string(_engine->getIsolate(), _value.constGet()); + Q_ASSERT(*string != nullptr); + return QString(*string); +} + +quint16 ScriptValueV8Wrapper::toUInt16() const { + v8::Local *integer; + Q_ASSERT(_value.constGet()->ToUint32(_value.constGetContext()).ToLocal(integer)); + return static_cast((*integer)->Value()); +} + +quint32 ScriptValueV8Wrapper::toUInt32() const { + v8::Local *integer; + Q_ASSERT(_value.constGet()->ToUint32(_value.constGetContext()).ToLocal(integer)); + return (*integer)->Value(); +} + +QVariant ScriptValueV8Wrapper::toVariant() const { + QVariant dest; + if (_engine->castValueToVariant(_value, dest, QMetaType::UnknownType)) { + return dest; + } else { + Q_ASSERT(false); + return QVariant(); + } +} + +QObject* ScriptValueV8Wrapper::toQObject() const { + QVariant dest; + if (_engine->castValueToVariant(_value, dest, QMetaType::QObjectStar)) { + return dest.value(); + } else { + Q_ASSERT(false); + return nullptr; + } +} + +bool ScriptValueV8Wrapper::equals(const ScriptValue& other) const { + ScriptValueV8Wrapper* unwrappedOther = unwrap(other); + //V8TODO: does this work with different contexts/isolates? + // in such case conversion will probably be necessary + if (!unwrappedOther) { + return false; + }else{ + if (_value.constGet()->Equals(unwrappedOther->toV8Value().constGetContext(), unwrappedOther->toV8Value().constGet()).IsNothing()) { + return false; + } else { + return _value.constGet()->Equals(unwrappedOther->toV8Value().constGetContext(), unwrappedOther->toV8Value().constGet()).FromJust(); + } + } +} + +bool ScriptValueV8Wrapper::isArray() const { + return _value.constGet()->IsArray(); +} + +bool ScriptValueV8Wrapper::isBool() const { + return _value.constGet()->IsBoolean(); +} + +bool ScriptValueV8Wrapper::isError() const { + //V8TODO + return false; + //return _value.constGet()->IsError(); +} + +bool ScriptValueV8Wrapper::isFunction() const { + return _value.constGet()->IsFunction(); +} + +bool ScriptValueV8Wrapper::isNumber() const { + return _value.constGet()->IsNumber(); +} + +bool ScriptValueV8Wrapper::isNull() const { + return _value.constGet()->IsNull(); +} + +bool ScriptValueV8Wrapper::isObject() const { + return _value.constGet()->IsObject(); +} + +bool ScriptValueV8Wrapper::isString() const { + return _value.constGet()->IsString(); +} + +bool ScriptValueV8Wrapper::isUndefined() const { + return _value.constGet()->IsUndefined(); +} + +bool ScriptValueV8Wrapper::isValid() const { + //V8TODO + return true; + //return _value.constGet()->IsValid(); +} + +bool ScriptValueV8Wrapper::isVariant() const { + //V8TODO + return false; + //return _value.isVariant(); +} diff --git a/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.h b/libraries/script-engine/src/v8/ScriptValueV8Wrapper.h similarity index 76% rename from libraries/script-engine/src/qtscript/ScriptValueQtWrapper.h rename to libraries/script-engine/src/v8/ScriptValueV8Wrapper.h index bce207ddbf..f0b11bbb95 100644 --- a/libraries/script-engine/src/qtscript/ScriptValueQtWrapper.h +++ b/libraries/script-engine/src/v8/ScriptValueV8Wrapper.h @@ -1,9 +1,11 @@ // -// ScriptValueQtWrapper.h -// libraries/script-engine/src/qtscript +// ScriptValueV8Wrapper.h +// libraries/script-engine/src/v8 // // Created by Heather Anderson on 5/16/21. +// Modified for V8 by dr Karol Suprynowicz on 2022/10/08 // Copyright 2021 Vircadia contributors. +// Copyright 2022 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -12,27 +14,27 @@ /// @addtogroup ScriptEngine /// @{ -#ifndef hifi_ScriptValueQtWrapper_h -#define hifi_ScriptValueQtWrapper_h +#ifndef hifi_ScriptValueV8Wrapper_h +#define hifi_ScriptValueV8Wrapper_h #include -#include #include #include "../ScriptValue.h" -#include "ScriptEngineQtScript.h" +#include "ScriptEngineV8.h" -/// [QtScript] Implements ScriptValue for QtScript and translates calls for QScriptValue -class ScriptValueQtWrapper final : public ScriptValueProxy { +/// [V8] Implements ScriptValue for V8 and translates calls for V8ScriptValue +class ScriptValueV8Wrapper final : public ScriptValueProxy { public: // construction - inline ScriptValueQtWrapper(ScriptEngineQtScript* engine, const QScriptValue& value) : + inline ScriptValueV8Wrapper(ScriptEngineV8* engine, const V8ScriptValue& value) : _engine(engine), _value(value) {} - inline ScriptValueQtWrapper(ScriptEngineQtScript* engine, QScriptValue&& value) : +// _engine(engine), _value(std::move(value.copy())) {} + inline ScriptValueV8Wrapper(ScriptEngineV8* engine, V8ScriptValue&& value) : _engine(engine), _value(std::move(value)) {} - static ScriptValueQtWrapper* unwrap(const ScriptValue& val); - inline const QScriptValue& toQtValue() const { return _value; } - static QScriptValue fullUnwrap(ScriptEngineQtScript* engine, const ScriptValue& value); + static ScriptValueV8Wrapper* unwrap(const ScriptValue& val); + inline const V8ScriptValue& toV8Value() const { return _value; } + static V8ScriptValue fullUnwrap(ScriptEngineV8* engine, const ScriptValue& value); public: virtual void release() override; @@ -84,13 +86,13 @@ public: // ScriptValue implementation virtual QObject* toQObject() const override; private: // helper functions - QScriptValue fullUnwrap(const ScriptValue& value) const; + V8ScriptValue fullUnwrap(const ScriptValue& value) const; private: // storage - QPointer _engine; - QScriptValue _value; + ScriptEngineV8 *_engine; + V8ScriptValue _value; }; -#endif // hifi_ScriptValueQtWrapper_h +#endif // hifi_ScriptValueV8Wrapper_h /// @} diff --git a/libraries/script-engine/src/qtscript/TypedArrayPrototype.cpp b/libraries/script-engine/src/v8/TypedArrayPrototype.cpp similarity index 81% rename from libraries/script-engine/src/qtscript/TypedArrayPrototype.cpp rename to libraries/script-engine/src/v8/TypedArrayPrototype.cpp index 17da12b845..762256a346 100644 --- a/libraries/script-engine/src/qtscript/TypedArrayPrototype.cpp +++ b/libraries/script-engine/src/v8/TypedArrayPrototype.cpp @@ -15,17 +15,18 @@ #include "TypedArrays.h" -Q_DECLARE_METATYPE(QByteArray*) +// V8TODO +/*Q_DECLARE_METATYPE(QByteArray*) TypedArrayPrototype::TypedArrayPrototype(QObject* parent) : QObject(parent) { } QByteArray* TypedArrayPrototype::thisArrayBuffer() const { - QScriptValue bufferObject = thisObject().data().property(BUFFER_PROPERTY_NAME); + V8ScriptValue bufferObject = thisObject().data().property(BUFFER_PROPERTY_NAME); return qscriptvalue_cast(bufferObject.data()); } -void TypedArrayPrototype::set(QScriptValue array, qint32 offset) { +void TypedArrayPrototype::set(V8ScriptValue array, qint32 offset) { TypedArray* typedArray = static_cast(parent()); if (array.isArray() || typedArray) { if (offset < 0) { @@ -44,9 +45,9 @@ void TypedArrayPrototype::set(QScriptValue array, qint32 offset) { } } -QScriptValue TypedArrayPrototype::subarray(qint32 begin) { +V8ScriptValue TypedArrayPrototype::subarray(qint32 begin) { TypedArray* typedArray = static_cast(parent()); - QScriptValue arrayBuffer = thisObject().data().property(typedArray->_bufferName); + V8ScriptValue arrayBuffer = thisObject().data().property(typedArray->_bufferName); qint32 byteOffset = thisObject().data().property(typedArray->_byteOffsetName).toInt32(); qint32 length = thisObject().data().property(typedArray->_lengthName).toInt32(); qint32 bytesPerElement = typedArray->_bytesPerElement; @@ -61,9 +62,9 @@ QScriptValue TypedArrayPrototype::subarray(qint32 begin) { return typedArray->newInstance(arrayBuffer, byteOffset, length - begin); } -QScriptValue TypedArrayPrototype::subarray(qint32 begin, qint32 end) { +V8ScriptValue TypedArrayPrototype::subarray(qint32 begin, qint32 end) { TypedArray* typedArray = static_cast(parent()); - QScriptValue arrayBuffer = thisObject().data().property(typedArray->_bufferName); + V8ScriptValue arrayBuffer = thisObject().data().property(typedArray->_bufferName); qint32 byteOffset = thisObject().data().property(typedArray->_byteOffsetName).toInt32(); qint32 length = thisObject().data().property(typedArray->_lengthName).toInt32(); qint32 bytesPerElement = typedArray->_bytesPerElement; @@ -83,9 +84,9 @@ QScriptValue TypedArrayPrototype::subarray(qint32 begin, qint32 end) { return typedArray->newInstance(arrayBuffer, byteOffset, length); } -QScriptValue TypedArrayPrototype::get(quint32 index) { +V8ScriptValue TypedArrayPrototype::get(quint32 index) { TypedArray* typedArray = static_cast(parent()); - QScriptString name = engine()->toStringHandle(QString::number(index)); + V8ScriptString name = engine()->toStringHandle(QString::number(index)); uint id; QScriptClass::QueryFlags flags = typedArray->queryProperty(thisObject(), name, @@ -93,13 +94,13 @@ QScriptValue TypedArrayPrototype::get(quint32 index) { if (QScriptClass::HandlesReadAccess & flags) { return typedArray->property(thisObject(), name, id); } - return QScriptValue(); + return V8ScriptValue(); } -void TypedArrayPrototype::set(quint32 index, QScriptValue& value) { +void TypedArrayPrototype::set(quint32 index, V8ScriptValue& value) { TypedArray* typedArray = static_cast(parent()); - QScriptValue object = thisObject(); - QScriptString name = engine()->toStringHandle(QString::number(index)); + V8ScriptValue object = thisObject(); + V8ScriptString name = engine()->toStringHandle(QString::number(index)); uint id; QScriptClass::QueryFlags flags = typedArray->queryProperty(object, name, @@ -108,3 +109,4 @@ void TypedArrayPrototype::set(quint32 index, QScriptValue& value) { typedArray->setProperty(object, name, id, value); } } +*/ diff --git a/libraries/script-engine/src/qtscript/TypedArrayPrototype.h b/libraries/script-engine/src/v8/TypedArrayPrototype.h similarity index 50% rename from libraries/script-engine/src/qtscript/TypedArrayPrototype.h rename to libraries/script-engine/src/v8/TypedArrayPrototype.h index ff0ea2fec6..1e43ffe9d7 100644 --- a/libraries/script-engine/src/qtscript/TypedArrayPrototype.h +++ b/libraries/script-engine/src/v8/TypedArrayPrototype.h @@ -16,26 +16,28 @@ #define hifi_TypedArrayPrototype_h #include -#include -#include -/// [QtScript] The javascript functions associated with a TypedArray instance prototype -class TypedArrayPrototype : public QObject, public QScriptable { +#include "V8Types.h" +#include "../Scriptable.h" + +// V8TODO +/// [V8] The javascript functions associated with a TypedArray instance prototype +/*class TypedArrayPrototype : public QObject, public Scriptable { Q_OBJECT public: TypedArrayPrototype(QObject* parent = NULL); public slots: - void set(QScriptValue array, qint32 offset = 0); - QScriptValue subarray(qint32 begin); - QScriptValue subarray(qint32 begin, qint32 end); + void set(V8ScriptValue array, qint32 offset = 0); + V8ScriptValue subarray(qint32 begin); + V8ScriptValue subarray(qint32 begin, qint32 end); - QScriptValue get(quint32 index); - void set(quint32 index, QScriptValue& value); + V8ScriptValue get(quint32 index); + void set(quint32 index, V8ScriptValue& value); private: QByteArray* thisArrayBuffer() const; }; - +*/ #endif // hifi_TypedArrayPrototype_h /// @} diff --git a/libraries/script-engine/src/qtscript/TypedArrays.cpp b/libraries/script-engine/src/v8/TypedArrays.cpp similarity index 66% rename from libraries/script-engine/src/qtscript/TypedArrays.cpp rename to libraries/script-engine/src/v8/TypedArrays.cpp index 8143dfb1fa..86e9c87951 100644 --- a/libraries/script-engine/src/qtscript/TypedArrays.cpp +++ b/libraries/script-engine/src/v8/TypedArrays.cpp @@ -18,17 +18,18 @@ #include #include "ArrayBufferClass.h" -#include "ScriptEngineQtScript.h" +#include "ScriptEngineV8.h" #include "TypedArrayPrototype.h" -Q_DECLARE_METATYPE(QByteArray*) +// V8TODO +/*Q_DECLARE_METATYPE(QByteArray*) -TypedArray::TypedArray(ScriptEngineQtScript* scriptEngine, QString name) : ArrayBufferViewClass(scriptEngine) { +TypedArray::TypedArray(ScriptEngineV8* scriptEngine, QString name) : ArrayBufferViewClass(scriptEngine) { _bytesPerElementName = engine()->toStringHandle(BYTES_PER_ELEMENT_PROPERTY_NAME.toLatin1()); _lengthName = engine()->toStringHandle(LENGTH_PROPERTY_NAME.toLatin1()); _name = engine()->toStringHandle(name.toLatin1()); - QScriptValue global = engine()->globalObject(); + V8ScriptValue global = engine()->globalObject(); // build prototype _proto = engine()->newQObject(new TypedArrayPrototype(this), @@ -44,30 +45,30 @@ TypedArray::TypedArray(ScriptEngineQtScript* scriptEngine, QString name) : Array engine()->globalObject().setProperty(_name, _ctor); } -QScriptValue TypedArray::newInstance(quint32 length) { +V8ScriptValue TypedArray::newInstance(quint32 length) { ArrayBufferClass* array = getScriptEngine()->getArrayBufferClass(); - QScriptValue buffer = array->newInstance(length * _bytesPerElement); + V8ScriptValue buffer = array->newInstance(length * _bytesPerElement); return newInstance(buffer, 0, length); } -QScriptValue TypedArray::newInstance(QScriptValue array) { +V8ScriptValue TypedArray::newInstance(V8ScriptValue array) { const QString ARRAY_LENGTH_HANDLE = "length"; if (array.property(ARRAY_LENGTH_HANDLE).isValid()) { quint32 length = array.property(ARRAY_LENGTH_HANDLE).toInt32(); - QScriptValue newArray = newInstance(length); + V8ScriptValue newArray = newInstance(length); for (quint32 i = 0; i < length; ++i) { - QScriptValue value = array.property(QString::number(i)); + V8ScriptValue value = array.property(QString::number(i)); setProperty(newArray, engine()->toStringHandle(QString::number(i)), - i * _bytesPerElement, (value.isNumber()) ? value : QScriptValue(0)); + i * _bytesPerElement, (value.isNumber()) ? value : V8ScriptValue(0)); } return newArray; } engine()->evaluate("throw \"ArgumentError: not an array\""); - return QScriptValue(); + return V8ScriptValue(); } -QScriptValue TypedArray::newInstance(QScriptValue buffer, quint32 byteOffset, quint32 length) { - QScriptValue data = engine()->newObject(); +V8ScriptValue TypedArray::newInstance(V8ScriptValue buffer, quint32 byteOffset, quint32 length) { + V8ScriptValue data = engine()->newObject(); data.setProperty(_bufferName, buffer); data.setProperty(_byteOffsetName, byteOffset); data.setProperty(_byteLengthName, length * _bytesPerElement); @@ -76,17 +77,17 @@ QScriptValue TypedArray::newInstance(QScriptValue buffer, quint32 byteOffset, qu return engine()->newObject(this, data); } -QScriptValue TypedArray::construct(QScriptContext* context, QScriptEngine* engine) { +V8ScriptValue TypedArray::construct(V8ScriptContext* context, QScriptEngine* engine) { TypedArray* cls = qscriptvalue_cast(context->callee().data()); if (!cls) { - return QScriptValue(); + return V8ScriptValue(); } if (context->argumentCount() == 0) { return cls->newInstance(0); } - QScriptValue newObject; - QScriptValue bufferArg = context->argument(0); + V8ScriptValue newObject; + V8ScriptValue bufferArg = context->argument(0); QByteArray* arrayBuffer = qscriptvalue_cast(bufferArg.data()); // parse arguments @@ -99,14 +100,14 @@ QScriptValue TypedArray::construct(QScriptContext* context, QScriptEngine* engin quint32 length = arrayBuffer->size() / cls->_bytesPerElement; newObject = cls->newInstance(bufferArg, 0, length); } else { - QScriptValue byteOffsetArg = context->argument(1); + V8ScriptValue byteOffsetArg = context->argument(1); if (!byteOffsetArg.isNumber()) { engine->evaluate("throw \"ArgumentError: 2nd arg is not a number\""); - return QScriptValue(); + return V8ScriptValue(); } if (byteOffsetArg.toInt32() < 0 || byteOffsetArg.toInt32() > arrayBuffer->size()) { engine->evaluate("throw \"RangeError: byteOffset out of range\""); - return QScriptValue(); + return V8ScriptValue(); } if (byteOffsetArg.toInt32() % cls->_bytesPerElement != 0) { engine->evaluate("throw \"RangeError: byteOffset not a multiple of BYTES_PER_ELEMENT\""); @@ -122,15 +123,15 @@ QScriptValue TypedArray::construct(QScriptContext* context, QScriptEngine* engin newObject = cls->newInstance(bufferArg, byteOffset, length); } else { - QScriptValue lengthArg = (context->argumentCount() > 2) ? context->argument(2) : QScriptValue(); + V8ScriptValueee lengthArg = (context->argumentCount() > 2) ? context->argument(2) : V8ScriptValue(); if (!lengthArg.isNumber()) { engine->evaluate("throw \"ArgumentError: 3nd arg is not a number\""); - return QScriptValue(); + return V8ScriptValue(); } if (lengthArg.toInt32() < 0 || byteOffsetArg.toInt32() + lengthArg.toInt32() * (qint32)(cls->_bytesPerElement) > arrayBuffer->size()) { engine->evaluate("throw \"RangeError: byteLength out of range\""); - return QScriptValue(); + return V8ScriptValue(); } quint32 length = lengthArg.toInt32(); @@ -154,8 +155,8 @@ QScriptValue TypedArray::construct(QScriptContext* context, QScriptEngine* engin return newObject; } -QScriptClass::QueryFlags TypedArray::queryProperty(const QScriptValue& object, - const QScriptString& name, +QScriptClass::QueryFlags TypedArray::queryProperty(const V8ScriptValue& object, + const V8ScriptString& name, QueryFlags flags, uint* id) { if (name == _bytesPerElementName || name == _lengthName) { return flags &= HandlesReadAccess; // Only keep read access flags @@ -175,10 +176,10 @@ QScriptClass::QueryFlags TypedArray::queryProperty(const QScriptValue& object, return ArrayBufferViewClass::queryProperty(object, name, flags, id); } -QScriptValue TypedArray::property(const QScriptValue& object, - const QScriptString& name, uint id) { +V8ScriptValue TypedArray::property(const V8ScriptValue& object, + const V8ScriptString& name, uint id) { if (name == _bytesPerElementName) { - return QScriptValue(_bytesPerElement); + return V8ScriptValue(_bytesPerElement); } if (name == _lengthName) { return object.data().property(_lengthName); @@ -186,16 +187,16 @@ QScriptValue TypedArray::property(const QScriptValue& object, return ArrayBufferViewClass::property(object, name, id); } -QScriptValue::PropertyFlags TypedArray::propertyFlags(const QScriptValue& object, - const QScriptString& name, uint id) { - return QScriptValue::Undeletable; +V8ScriptValue::PropertyFlags TypedArray::propertyFlags(const V8ScriptValue& object, + const V8ScriptString& name, uint id) { + return V8ScriptValue::Undeletable; } QString TypedArray::name() const { return _name.toString(); } -QScriptValue TypedArray::prototype() const { +V8ScriptValue TypedArray::prototype() const { return _proto; } @@ -207,7 +208,7 @@ void TypedArray::setBytesPerElement(quint32 bytesPerElement) { // templated helper functions // don't work for floats as they require single precision settings template -QScriptValue propertyHelper(const QByteArray* arrayBuffer, const QScriptString& name, uint id) { +V8ScriptValue propertyHelper(const QByteArray* arrayBuffer, const V8ScriptString& name, uint id) { bool ok = false; name.toArrayIndex(&ok); @@ -220,11 +221,11 @@ QScriptValue propertyHelper(const QByteArray* arrayBuffer, const QScriptString& stream >> result; return result; } - return QScriptValue(); + return V8ScriptValue(); } template -void setPropertyHelper(QByteArray* arrayBuffer, const QScriptString& name, uint id, const QScriptValue& value) { +void setPropertyHelper(QByteArray* arrayBuffer, const V8ScriptString& name, uint id, const V8ScriptValue& value) { if (arrayBuffer && value.isNumber()) { QDataStream stream(arrayBuffer, QIODevice::ReadWrite); stream.skipRawData(id); @@ -234,50 +235,50 @@ void setPropertyHelper(QByteArray* arrayBuffer, const QScriptString& name, uint } } -Int8ArrayClass::Int8ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, INT_8_ARRAY_CLASS_NAME) { +Int8ArrayClass::Int8ArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, INT_8_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(qint8)); } -QScriptValue Int8ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Int8ArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); - QScriptValue result = propertyHelper(arrayBuffer, name, id); + V8ScriptValue result = propertyHelper(arrayBuffer, name, id); return (result.isValid()) ? result : TypedArray::property(object, name, id); } -void Int8ArrayClass::setProperty(QScriptValue &object, const QScriptString &name, - uint id, const QScriptValue& value) { +void Int8ArrayClass::setProperty(V8ScriptValue &object, const V8ScriptString &name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); setPropertyHelper(ba, name, id, value); } -Uint8ArrayClass::Uint8ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, UINT_8_ARRAY_CLASS_NAME) { +Uint8ArrayClass::Uint8ArrayClass(ScriptEngineV8ptEngine) : TypedArray(scriptEngine, UINT_8_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(quint8)); } -QScriptValue Uint8ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Uint8ArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); - QScriptValue result = propertyHelper(arrayBuffer, name, id); + V8ScriptValue result = propertyHelper(arrayBuffer, name, id); return (result.isValid()) ? result : TypedArray::property(object, name, id); } -void Uint8ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Uint8ArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); setPropertyHelper(ba, name, id, value); } -Uint8ClampedArrayClass::Uint8ClampedArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, UINT_8_CLAMPED_ARRAY_CLASS_NAME) { +Uint8ClampedArrayClass::Uint8ClampedArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, UINT_8_CLAMPED_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(quint8)); } -QScriptValue Uint8ClampedArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Uint8ClampedArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); - QScriptValue result = propertyHelper(arrayBuffer, name, id); + V8ScriptValue result = propertyHelper(arrayBuffer, name, id); return (result.isValid()) ? result : TypedArray::property(object, name, id); } -void Uint8ClampedArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Uint8ClampedArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); if (ba && value.isNumber()) { QDataStream stream(ba, QIODevice::ReadWrite); @@ -292,76 +293,76 @@ void Uint8ClampedArrayClass::setProperty(QScriptValue& object, const QScriptStri } } -Int16ArrayClass::Int16ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, INT_16_ARRAY_CLASS_NAME) { +Int16ArrayClass::Int16ArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, INT_16_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(qint16)); } -QScriptValue Int16ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Int16ArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); - QScriptValue result = propertyHelper(arrayBuffer, name, id); + V8ScriptValue result = propertyHelper(arrayBuffer, name, id); return (result.isValid()) ? result : TypedArray::property(object, name, id); } -void Int16ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Int16ArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); setPropertyHelper(ba, name, id, value); } -Uint16ArrayClass::Uint16ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, UINT_16_ARRAY_CLASS_NAME) { +Uint16ArrayClass::Uint16ArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, UINT_16_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(quint16)); } -QScriptValue Uint16ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Uint16ArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); - QScriptValue result = propertyHelper(arrayBuffer, name, id); + V8ScriptValue result = propertyHelper(arrayBuffer, name, id); return (result.isValid()) ? result : TypedArray::property(object, name, id); } -void Uint16ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Uint16ArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); setPropertyHelper(ba, name, id, value); } -Int32ArrayClass::Int32ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, INT_32_ARRAY_CLASS_NAME) { +Int32ArrayClass::Int32ArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, INT_32_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(qint32)); } -QScriptValue Int32ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Int32ArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); - QScriptValue result = propertyHelper(arrayBuffer, name, id); + V8ScriptValue result = propertyHelper(arrayBuffer, name, id); return (result.isValid()) ? result : TypedArray::property(object, name, id); } -void Int32ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Int32ArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); setPropertyHelper(ba, name, id, value); } -Uint32ArrayClass::Uint32ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, UINT_32_ARRAY_CLASS_NAME) { +Uint32ArrayClass::Uint32ArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, UINT_32_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(quint32)); } -QScriptValue Uint32ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Uint32ArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data()); - QScriptValue result = propertyHelper(arrayBuffer, name, id); + V8ScriptValue result = propertyHelper(arrayBuffer, name, id); return (result.isValid()) ? result : TypedArray::property(object, name, id); } -void Uint32ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Uint32ArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); setPropertyHelper(ba, name, id, value); } -Float32ArrayClass::Float32ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, FLOAT_32_ARRAY_CLASS_NAME) { +Float32ArrayClass::Float32ArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, FLOAT_32_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(float)); } -QScriptValue Float32ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Float32ArrayClass::property(const V8ScriptValuee& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data());bool ok = false; name.toArrayIndex(&ok); @@ -374,15 +375,15 @@ QScriptValue Float32ArrayClass::property(const QScriptValue& object, const QScri float result; stream >> result; if (isNaN(result)) { - return QScriptValue(); + return V8ScriptValuee(); } return result; } return TypedArray::property(object, name, id); } -void Float32ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Float32ArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); if (ba && value.isNumber()) { QDataStream stream(ba, QIODevice::ReadWrite); @@ -394,11 +395,11 @@ void Float32ArrayClass::setProperty(QScriptValue& object, const QScriptString& n } } -Float64ArrayClass::Float64ArrayClass(ScriptEngineQtScript* scriptEngine) : TypedArray(scriptEngine, FLOAT_64_ARRAY_CLASS_NAME) { +Float64ArrayClass::Float64ArrayClass(ScriptEngineV8* scriptEngine) : TypedArray(scriptEngine, FLOAT_64_ARRAY_CLASS_NAME) { setBytesPerElement(sizeof(double)); } -QScriptValue Float64ArrayClass::property(const QScriptValue& object, const QScriptString& name, uint id) { +V8ScriptValue Float64ArrayClass::property(const V8ScriptValue& object, const V8ScriptString& name, uint id) { QByteArray* arrayBuffer = qscriptvalue_cast(object.data().property(_bufferName).data());bool ok = false; name.toArrayIndex(&ok); @@ -411,15 +412,15 @@ QScriptValue Float64ArrayClass::property(const QScriptValue& object, const QScri double result; stream >> result; if (isNaN(result)) { - return QScriptValue(); + return V8ScriptValue(); } return result; } return TypedArray::property(object, name, id); } -void Float64ArrayClass::setProperty(QScriptValue& object, const QScriptString& name, - uint id, const QScriptValue& value) { +void Float64ArrayClass::setProperty(V8ScriptValue& object, const V8ScriptString& name, + uint id, const V8ScriptValue& value) { QByteArray* ba = qscriptvalue_cast(object.data().property(_bufferName).data()); if (ba && value.isNumber()) { QDataStream stream(ba, QIODevice::ReadWrite); @@ -429,5 +430,5 @@ void Float64ArrayClass::setProperty(QScriptValue& object, const QScriptString& n stream << (double)value.toNumber(); } -} +}*/ diff --git a/libraries/script-engine/src/v8/TypedArrays.h b/libraries/script-engine/src/v8/TypedArrays.h new file mode 100644 index 0000000000..f02849b47a --- /dev/null +++ b/libraries/script-engine/src/v8/TypedArrays.h @@ -0,0 +1,155 @@ +// +// TypedArrays.h +// +// +// Created by Clement on 7/9/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 +// + +/// @addtogroup ScriptEngine +/// @{ + +#ifndef hifi_TypedArrays_h +#define hifi_TypedArrays_h + +// V8TODO +/*#include "ArrayBufferViewClass.h" + +static const QString BYTES_PER_ELEMENT_PROPERTY_NAME = "BYTES_PER_ELEMENT"; +static const QString LENGTH_PROPERTY_NAME = "length"; + +static const QString INT_8_ARRAY_CLASS_NAME = "Int8Array"; +static const QString UINT_8_ARRAY_CLASS_NAME = "Uint8Array"; +static const QString UINT_8_CLAMPED_ARRAY_CLASS_NAME = "Uint8ClampedArray"; +static const QString INT_16_ARRAY_CLASS_NAME = "Int16Array"; +static const QString UINT_16_ARRAY_CLASS_NAME = "Uint16Array"; +static const QString INT_32_ARRAY_CLASS_NAME = "Int32Array"; +static const QString UINT_32_ARRAY_CLASS_NAME = "Uint32Array"; +static const QString FLOAT_32_ARRAY_CLASS_NAME = "Float32Array"; +static const QString FLOAT_64_ARRAY_CLASS_NAME = "Float64Array"; + +/// [QtScript] Implements the TypedArray scripting class +class TypedArray : public ArrayBufferViewClass { + Q_OBJECT +public: + TypedArray(ScriptEngineV8* scriptEngine, QString name); + virtual V8ScriptValue newInstance(quint32 length); + virtual V8ScriptValue newInstance(V8ScriptValue array); + virtual V8ScriptValue newInstance(V8ScriptValue buffer, quint32 byteOffset, quint32 length); + + virtual QueryFlags queryProperty(const V8ScriptValue& object, + const V8ScriptString& name, + QueryFlags flags, uint* id) override; + virtual V8ScriptValue property(const V8ScriptValue& object, + const V8ScriptString& name, uint id) override; + virtual void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override = 0; + virtual V8ScriptValue::PropertyFlags propertyFlags(const V8ScriptValue& object, + const V8ScriptString& name, uint id) override; + + QString name() const override; + V8ScriptValue prototype() const override; + +protected: + static V8ScriptValue construct(V8ScriptContext* context, QScriptEngine* engine); + + void setBytesPerElement(quint32 bytesPerElement); + + V8ScriptValue _proto; + V8ScriptValue _ctor; + + V8ScriptString _name; + V8ScriptString _bytesPerElementName; + V8ScriptString _lengthName; + + quint32 _bytesPerElement; + + friend class TypedArrayPrototype; +}; + +class Int8ArrayClass : public TypedArray { + Q_OBJECT +public: + Int8ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Uint8ArrayClass : public TypedArray { + Q_OBJECT +public: + Uint8ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Uint8ClampedArrayClass : public TypedArray { + Q_OBJECT +public: + Uint8ClampedArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Int16ArrayClass : public TypedArray { + Q_OBJECT +public: + Int16ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Uint16ArrayClass : public TypedArray { + Q_OBJECT +public: + Uint16ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Int32ArrayClass : public TypedArray { + Q_OBJECT +public: + Int32ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Uint32ArrayClass : public TypedArray { + Q_OBJECT +public: + Uint32ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Float32ArrayClass : public TypedArray { + Q_OBJECT +public: + Float32ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; + +class Float64ArrayClass : public TypedArray { + Q_OBJECT +public: + Float64ArrayClass(ScriptEngineV8* scriptEngine); + + V8ScriptValue property(const V8ScriptValue& object, const V8ScriptString& name, uint id) override; + void setProperty(V8ScriptValue& object, const V8ScriptString& name, uint id, const V8ScriptValue& value) override; +}; +*/ +#endif // hifi_TypedArrays_h + +/// @} diff --git a/libraries/script-engine/src/v8/V8Types.h b/libraries/script-engine/src/v8/V8Types.h new file mode 100644 index 0000000000..92582b3df7 --- /dev/null +++ b/libraries/script-engine/src/v8/V8Types.h @@ -0,0 +1,76 @@ +// +// V8Types.h +// libraries/script-engine/src/v8 +// +// Created by dr Karol Suprynowicz on 2022/10/08 +// Copyright 2022 Overte e.V. +// +// 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_V8Types_h +#define hifi_V8Types_h + +#include + +#include +#include + +template +class V8ScriptValueTemplate { +public: + V8ScriptValueTemplate() = delete; + //V8ScriptValueTemplate(v8::Isolate *isolate, v8::Local value) : _isolate(isolate) { + //_value.reset(v8::UniquePersistent::New(_isolate, value)); + //}; + V8ScriptValueTemplate(v8::Isolate *isolate, const v8::Local value) : _isolate(isolate), _context(isolate, isolate->GetCurrentContext()) { + //_value.reset(_isolate, value); + _value.reset(new v8::UniquePersistent(_isolate, std::move(value))); + }; + v8::Local get() {return _value.get()->Get(_isolate);}; + const v8::Local constGet() const {return _value.get()->Get(_isolate);}; + V8ScriptValueTemplate&& copy() const {return new V8ScriptValueTemplate(_isolate, v8::Local::New(_isolate, constGet()));}; + const v8::Local constGetContext() const { + v8::EscapableHandleScope handleScope(_isolate); + return handleScope.Escape(_context.Get(_isolate)); + }; + const v8::Isolate* constGetIsolate() const { return _isolate;}; + v8::Isolate* getIsolate() { return _isolate;}; + //v8::Persistent>& getContext() { return _context;}; + v8::Local getContext() { + v8::EscapableHandleScope handleScope(_isolate); + return handleScope.Escape(_context.Get(_isolate)); + }; + void reset(v8::Isolate *isolate, v8::Local) {}; +private: + std::shared_ptr> _value; + // V8TODO: maybe make it weak + // does it need , CopyablePersistentTraits? + // V8TODO: is context needed at all? + v8::Isolate *_isolate; + v8::Persistent> _context; +}; + +typedef V8ScriptValueTemplate V8ScriptValue; +typedef V8ScriptValueTemplate V8ScriptProgram; +// V8TODO: Maybe weak? +typedef v8::Persistent V8ScriptContext; + +class V8ScriptString : public V8ScriptValueTemplate { +public: + V8ScriptString() = delete; + V8ScriptString(v8::Isolate *isolate, const v8::Local value) : V8ScriptValueTemplate(isolate, value) {}; + const QString toQString() const { + return QString(*v8::String::Utf8Value(const_cast(constGetIsolate()), constGet())); + }; + bool operator==(const V8ScriptString& string) const { + return constGet()->StringEquals(string.constGet()); + } +}; + +inline uint qHash(const V8ScriptString &key, uint seed = 0) { + return qHash(key.toQString(), seed); +}; + +#endif