diff options
-rw-r--r-- | src/qml/jsapi/qjsengine.cpp | 121 | ||||
-rw-r--r-- | src/qml/jsapi/qjsengine.h | 16 | ||||
-rw-r--r-- | src/qml/qml/v8/qqmlbuiltinfunctions.cpp | 60 | ||||
-rw-r--r-- | src/qml/qml/v8/qqmlbuiltinfunctions_p.h | 3 | ||||
-rw-r--r-- | src/qml/qml/v8/qv8engine.cpp | 5 | ||||
-rw-r--r-- | tests/auto/qml/qjsengine/tst_qjsengine.cpp | 119 |
6 files changed, 281 insertions, 43 deletions
diff --git a/src/qml/jsapi/qjsengine.cpp b/src/qml/jsapi/qjsengine.cpp index b217d3fb4c..5ccbccebad 100644 --- a/src/qml/jsapi/qjsengine.cpp +++ b/src/qml/jsapi/qjsengine.cpp @@ -160,8 +160,82 @@ Q_DECLARE_METATYPE(QList<int>) \snippet code/src_script_qjsengine.cpp 5 - \sa QJSValue, {Making Applications Scriptable} + \section1 Extensions + QJSEngine provides a compliant ECMAScript implementation. By default, + familiar utilities like logging are not available, but they can can be + installed via the \l installExtensions() function. + + \sa QJSValue, {Making Applications Scriptable}, + {List of JavaScript Objects and Functions} + +*/ + +/*! + \enum QJSEngine::Extension + + This enum is used to specify extensions to be installed via + \l installExtensions(). + + \value TranslationExtension Indicates that translation functions (\c qsTr(), + for example) should be installed. + + \value ConsoleExtension Indicates that console functions (\c console.log(), + for example) should be installed. + + \value GarbageCollectionExtension Indicates that garbage collection + functions (\c gc(), for example) should be installed. + + \value AllExtensions Indicates that all extension should be installed. + + \b TranslationExtension + + The relation between script translation functions and C++ translation + functions is described in the following table: + + \table + \header \li Script Function \li Corresponding C++ Function + \row \li qsTr() \li QObject::tr() + \row \li QT_TR_NOOP() \li QT_TR_NOOP() + \row \li qsTranslate() \li QCoreApplication::translate() + \row \li QT_TRANSLATE_NOOP() \li QT_TRANSLATE_NOOP() + \row \li qsTrId() \li qtTrId() + \row \li QT_TRID_NOOP() \li QT_TRID_NOOP() + \endtable + + This flag also adds an \c arg() function to the string prototype. + + For more information, see the \l {Internationalization with Qt} + documentation. + + \b ConsoleExtension + + The \l {Console API}{console} object implements a subset of the + \l {https://developer.mozilla.org/en-US/docs/Web/API/Console}{Console API}, + which provides familiar logging functions, such as \c console.log(). + + The list of functions added is as follows: + + \list + \li \c console.assert() + \li \c console.debug() + \li \c console.exception() + \li \c console.info() + \li \c console.log() (equivalent to \c console.debug()) + \li \c console.error() + \li \c console.time() + \li \c console.timeEnd() + \li \c console.trace() + \li \c console.count() + \li \c console.warn() + \li \c {print()} (equivalent to \c console.debug()) + \endlist + + For more information, see the \l {Console API} documentation. + + \b GarbageCollectionExtension + + The \c gc() function is equivalent to calling \l collectGarbage(). */ QT_BEGIN_NAMESPACE @@ -234,8 +308,11 @@ void QJSEngine::collectGarbage() d->m_v4Engine->memoryManager->runGC(); } +#if QT_DEPRECATED_SINCE(5, 6) + /*! \since 5.4 + \obsolete Installs translator functions on the given \a object, or on the Global Object if no object is specified. @@ -259,11 +336,37 @@ void QJSEngine::collectGarbage() */ void QJSEngine::installTranslatorFunctions(const QJSValue &object) { + installExtensions(TranslationExtension, object); +} + +#endif // QT_DEPRECATED_SINCE(5, 6) + + +/*! + \since 5.6 + + Installs JavaScript \a extensions to add functionality that is not + available in a standard ECMAScript implementation. + + The extensions are installed on the given \a object, or on the + \l {globalObject()}{Global Object} if no object is specified. + + Several extensions can be installed at once by \c {OR}-ing the enum values: + + \code + installExtensions(QJSEngine::TranslationExtension | QJSEngine::ConsoleExtension); + \endcode + + \sa Extension +*/ +void QJSEngine::installExtensions(QJSEngine::Extensions extensions, const QJSValue &object) +{ QV4::ExecutionEngine *otherEngine = QJSValuePrivate::engine(&object); if (otherEngine && otherEngine != d->m_v4Engine) { - qWarning("QJSEngine: Trying to install a translator function from a different engine"); + qWarning("QJSEngine: Trying to install extensions from a different engine"); return; } + QV4::Scope scope(d->m_v4Engine); QV4::ScopedObject obj(scope); QV4::Value *val = QJSValuePrivate::getValue(&object); @@ -271,18 +374,8 @@ void QJSEngine::installTranslatorFunctions(const QJSValue &object) obj = val; if (!obj) obj = scope.engine->globalObject; -#ifndef QT_NO_TRANSLATION - obj->defineDefaultProperty(QStringLiteral("qsTranslate"), QV4::GlobalExtensions::method_qsTranslate); - obj->defineDefaultProperty(QStringLiteral("QT_TRANSLATE_NOOP"), QV4::GlobalExtensions::method_qsTranslateNoOp); - obj->defineDefaultProperty(QStringLiteral("qsTr"), QV4::GlobalExtensions::method_qsTr); - obj->defineDefaultProperty(QStringLiteral("QT_TR_NOOP"), QV4::GlobalExtensions::method_qsTrNoOp); - obj->defineDefaultProperty(QStringLiteral("qsTrId"), QV4::GlobalExtensions::method_qsTrId); - obj->defineDefaultProperty(QStringLiteral("QT_TRID_NOOP"), QV4::GlobalExtensions::method_qsTrIdNoOp); - - // string prototype extension - scope.engine->stringPrototype()->defineDefaultProperty(QStringLiteral("arg"), - QV4::GlobalExtensions::method_string_arg); -#endif + + QV4::GlobalExtensions::init(obj, extensions); } /*! diff --git a/src/qml/jsapi/qjsengine.h b/src/qml/jsapi/qjsengine.h index 123eb727df..40b0a60369 100644 --- a/src/qml/jsapi/qjsengine.h +++ b/src/qml/jsapi/qjsengine.h @@ -81,7 +81,19 @@ public: void collectGarbage(); - void installTranslatorFunctions(const QJSValue &object = QJSValue()); +#if QT_DEPRECATED_SINCE(5, 6) + QT_DEPRECATED void installTranslatorFunctions(const QJSValue &object = QJSValue()); +#endif + + enum Extension { + TranslationExtension = 0x1, + ConsoleExtension = 0x2, + GarbageCollectionExtension = 0x4, + AllExtensions = 0xffffffff + }; + Q_DECLARE_FLAGS(Extensions, Extension) + + void installExtensions(Extensions extensions, const QJSValue &object = QJSValue()); QV8Engine *handle() const { return d; } @@ -102,6 +114,8 @@ private: friend class QV8Engine; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(QJSEngine::Extensions) + inline bool qjsvalue_cast_helper(const QJSValue &value, int type, void *ptr) { return QJSEngine::convertV2(value, type, ptr); diff --git a/src/qml/qml/v8/qqmlbuiltinfunctions.cpp b/src/qml/qml/v8/qqmlbuiltinfunctions.cpp index 3242833e22..5deac8343e 100644 --- a/src/qml/qml/v8/qqmlbuiltinfunctions.cpp +++ b/src/qml/qml/v8/qqmlbuiltinfunctions.cpp @@ -1390,27 +1390,30 @@ static QV4::ReturnedValue writeToConsole(ConsoleLogTypes logType, CallContext *c result.append(jsStack(v4)); } - static QLoggingCategory loggingCategory("qml"); + static QLoggingCategory qmlLoggingCategory("qml"); + static QLoggingCategory jsLoggingCategory("js"); + + QLoggingCategory *loggingCategory = v4->qmlEngine() ? &qmlLoggingCategory : &jsLoggingCategory; QV4::StackFrame frame = v4->currentStackFrame(); const QByteArray baSource = frame.source.toUtf8(); const QByteArray baFunction = frame.function.toUtf8(); - QMessageLogger logger(baSource.constData(), frame.line, baFunction.constData(), loggingCategory.categoryName()); + QMessageLogger logger(baSource.constData(), frame.line, baFunction.constData(), loggingCategory->categoryName()); switch (logType) { case Log: - if (loggingCategory.isDebugEnabled()) + if (loggingCategory->isDebugEnabled()) logger.debug("%s", result.toUtf8().constData()); break; case Info: - if (loggingCategory.isInfoEnabled()) + if (loggingCategory->isInfoEnabled()) logger.info("%s", result.toUtf8().constData()); break; case Warn: - if (loggingCategory.isWarningEnabled()) + if (loggingCategory->isWarningEnabled()) logger.warning("%s", result.toUtf8().constData()); break; case Error: - if (loggingCategory.isCriticalEnabled()) + if (loggingCategory->isCriticalEnabled()) logger.critical("%s", result.toUtf8().constData()); break; default: @@ -1442,6 +1445,9 @@ QV4::ReturnedValue ConsoleObject::method_profile(CallContext *ctx) { QV4::ExecutionEngine *v4 = ctx->d()->engine; + if (!v4->qmlEngine()) + return QV4::Encode::undefined(); // Not yet implemented for JavaScript. + QV4::StackFrame frame = v4->currentStackFrame(); const QByteArray baSource = frame.source.toUtf8(); const QByteArray baFunction = frame.function.toUtf8(); @@ -1461,6 +1467,9 @@ QV4::ReturnedValue ConsoleObject::method_profileEnd(CallContext *ctx) { QV4::ExecutionEngine *v4 = ctx->d()->engine; + if (!v4->qmlEngine()) + return QV4::Encode::undefined(); // Not yet implemented for JavaScript. + QV4::StackFrame frame = v4->currentStackFrame(); const QByteArray baSource = frame.source.toUtf8(); const QByteArray baFunction = frame.function.toUtf8(); @@ -1590,31 +1599,36 @@ QV4::ReturnedValue ConsoleObject::method_exception(CallContext *ctx) -void QV4::GlobalExtensions::init(QQmlEngine *qmlEngine, Object *globalObject) +void QV4::GlobalExtensions::init(Object *globalObject, QJSEngine::Extensions extensions) { ExecutionEngine *v4 = globalObject->engine(); Scope scope(v4); -#ifndef QT_NO_TRANSLATION - globalObject->defineDefaultProperty(QStringLiteral("qsTranslate"), method_qsTranslate); - globalObject->defineDefaultProperty(QStringLiteral("QT_TRANSLATE_NOOP"), method_qsTranslateNoOp); - globalObject->defineDefaultProperty(QStringLiteral("qsTr"), method_qsTr); - globalObject->defineDefaultProperty(QStringLiteral("QT_TR_NOOP"), method_qsTrNoOp); - globalObject->defineDefaultProperty(QStringLiteral("qsTrId"), method_qsTrId); - globalObject->defineDefaultProperty(QStringLiteral("QT_TRID_NOOP"), method_qsTrIdNoOp); -#endif + if (extensions.testFlag(QJSEngine::TranslationExtension)) { + #ifndef QT_NO_TRANSLATION + globalObject->defineDefaultProperty(QStringLiteral("qsTranslate"), QV4::GlobalExtensions::method_qsTranslate); + globalObject->defineDefaultProperty(QStringLiteral("QT_TRANSLATE_NOOP"), QV4::GlobalExtensions::method_qsTranslateNoOp); + globalObject->defineDefaultProperty(QStringLiteral("qsTr"), QV4::GlobalExtensions::method_qsTr); + globalObject->defineDefaultProperty(QStringLiteral("QT_TR_NOOP"), QV4::GlobalExtensions::method_qsTrNoOp); + globalObject->defineDefaultProperty(QStringLiteral("qsTrId"), QV4::GlobalExtensions::method_qsTrId); + globalObject->defineDefaultProperty(QStringLiteral("QT_TRID_NOOP"), QV4::GlobalExtensions::method_qsTrIdNoOp); + + // string prototype extension + scope.engine->stringPrototype()->defineDefaultProperty(QStringLiteral("arg"), QV4::GlobalExtensions::method_string_arg); + #endif + } - globalObject->defineDefaultProperty(QStringLiteral("print"), ConsoleObject::method_log); - globalObject->defineDefaultProperty(QStringLiteral("gc"), method_gc); + if (extensions.testFlag(QJSEngine::ConsoleExtension)) { + globalObject->defineDefaultProperty(QStringLiteral("print"), QV4::ConsoleObject::method_log); - ScopedObject console(scope, v4->memoryManager->allocObject<QV4::ConsoleObject>()); - globalObject->defineDefaultProperty(QStringLiteral("console"), console); - ScopedObject qt(scope, v4->memoryManager->allocObject<QV4::QtObject>(qmlEngine)); - globalObject->defineDefaultProperty(QStringLiteral("Qt"), qt); + QV4::ScopedObject console(scope, globalObject->engine()->memoryManager->allocObject<QV4::ConsoleObject>()); + globalObject->defineDefaultProperty(QStringLiteral("console"), console); + } - // string prototype extension - v4->stringPrototype()->defineDefaultProperty(QStringLiteral("arg"), method_string_arg); + if (extensions.testFlag(QJSEngine::GarbageCollectionExtension)) { + globalObject->defineDefaultProperty(QStringLiteral("gc"), QV4::GlobalExtensions::method_gc); + } } diff --git a/src/qml/qml/v8/qqmlbuiltinfunctions_p.h b/src/qml/qml/v8/qqmlbuiltinfunctions_p.h index bb05376fc0..d373fb6ee6 100644 --- a/src/qml/qml/v8/qqmlbuiltinfunctions_p.h +++ b/src/qml/qml/v8/qqmlbuiltinfunctions_p.h @@ -47,6 +47,7 @@ #include <private/qqmlglobal_p.h> #include <private/qv4functionobject_p.h> +#include <private/qjsengine_p.h> QT_BEGIN_NAMESPACE @@ -142,7 +143,7 @@ struct ConsoleObject : Object }; struct GlobalExtensions { - static void init(QQmlEngine *qmlEngine, Object *globalObject); + static void init(Object *globalObject, QJSEngine::Extensions extensions); #ifndef QT_NO_TRANSLATION static ReturnedValue method_qsTranslate(CallContext *ctx); diff --git a/src/qml/qml/v8/qv8engine.cpp b/src/qml/qml/v8/qv8engine.cpp index effc37a0eb..dae932e705 100644 --- a/src/qml/qml/v8/qv8engine.cpp +++ b/src/qml/qml/v8/qv8engine.cpp @@ -174,7 +174,10 @@ const QSet<QString> &QV8Engine::illegalNames() const void QV8Engine::initializeGlobal() { QV4::Scope scope(m_v4Engine); - QV4::GlobalExtensions::init(m_engine, m_v4Engine->globalObject); + QV4::GlobalExtensions::init(m_v4Engine->globalObject, QJSEngine::AllExtensions); + + QV4::ScopedObject qt(scope, m_v4Engine->memoryManager->allocObject<QV4::QtObject>(m_engine)); + m_v4Engine->globalObject->defineDefaultProperty(QStringLiteral("Qt"), qt); QQmlLocale::registerStringLocaleCompare(m_v4Engine); QQmlDateExtension::registerExtension(m_v4Engine); diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index 4c7dba9f90..39bc8b2e5f 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -155,7 +155,7 @@ private slots: void callConstants(); - void installTranslatorFunctions(); + void installTranslationFunctions(); void translateScript_data(); void translateScript(); void translateScript_crossScript(); @@ -172,6 +172,16 @@ private slots: void translateScriptUnicodeIdBased(); void translateFromBuiltinCallback(); + void installConsoleFunctions(); + void logging(); + void tracing(); + void asserts(); + void exceptions(); + + void installGarbageCollectionFunctions(); + + void installAllExtensions(); + void privateMethods(); void engineForObject(); @@ -1480,6 +1490,8 @@ void tst_QJSEngine::gcWithNestedDataStructure() // The GC must be able to traverse deeply nested objects, otherwise this // test would crash. QJSEngine eng; + eng.installExtensions(QJSEngine::GarbageCollectionExtension); + QJSValue ret = eng.evaluate( "function makeList(size)" "{" @@ -3171,7 +3183,7 @@ void tst_QJSEngine::callConstants() QCOMPARE(exceptionResult.toString(), QString("TypeError: true is not a function")); } -void tst_QJSEngine::installTranslatorFunctions() +void tst_QJSEngine::installTranslationFunctions() { QJSEngine eng; QJSValue global = eng.globalObject(); @@ -3182,7 +3194,7 @@ void tst_QJSEngine::installTranslatorFunctions() QVERIFY(global.property("qsTrId").isUndefined()); QVERIFY(global.property("QT_TRID_NOOP").isUndefined()); - eng.installTranslatorFunctions(); + eng.installExtensions(QJSEngine::TranslationExtension); QVERIFY(global.property("qsTranslate").isCallable()); QVERIFY(global.property("QT_TRANSLATE_NOOP").isCallable()); QVERIFY(global.property("qsTr").isCallable()); @@ -3597,6 +3609,107 @@ void tst_QJSEngine::translateFromBuiltinCallback() eng.evaluate("[10,20].forEach(foo)", "script.js"); } +void tst_QJSEngine::installConsoleFunctions() +{ + QJSEngine engine; + QJSValue global = engine.globalObject(); + QVERIFY(global.property("console").isUndefined()); + QVERIFY(global.property("print").isUndefined()); + + engine.installExtensions(QJSEngine::ConsoleExtension); + QVERIFY(global.property("console").isObject()); + QVERIFY(global.property("print").isCallable()); +} + +void tst_QJSEngine::logging() +{ + QLoggingCategory loggingCategory("js"); + QVERIFY(loggingCategory.isDebugEnabled()); + QVERIFY(loggingCategory.isWarningEnabled()); + QVERIFY(loggingCategory.isCriticalEnabled()); + + QJSEngine engine; + engine.installExtensions(QJSEngine::ConsoleExtension); + + QTest::ignoreMessage(QtDebugMsg, "console.debug"); + engine.evaluate("console.debug('console.debug')"); + QTest::ignoreMessage(QtDebugMsg, "console.log"); + engine.evaluate("console.log('console.log')"); + QTest::ignoreMessage(QtInfoMsg, "console.info"); + engine.evaluate("console.info('console.info')"); + QTest::ignoreMessage(QtWarningMsg, "console.warn"); + engine.evaluate("console.warn('console.warn')"); + QTest::ignoreMessage(QtCriticalMsg, "console.error"); + engine.evaluate("console.error('console.error')"); + + QTest::ignoreMessage(QtDebugMsg, ": 1"); + engine.evaluate("console.count()"); + + QTest::ignoreMessage(QtDebugMsg, ": 2"); + engine.evaluate("console.count()"); +} + +void tst_QJSEngine::tracing() +{ + QJSEngine engine; + engine.installExtensions(QJSEngine::ConsoleExtension); + + QTest::ignoreMessage(QtDebugMsg, "%entry (:1)"); + engine.evaluate("console.trace()"); + + QTest::ignoreMessage(QtDebugMsg, "a (:1)\nb (:1)\nc (:1)\n%entry (:1)"); + engine.evaluate("function a() { console.trace(); } function b() { a(); } function c() { b(); }"); + engine.evaluate("c()"); +} + +void tst_QJSEngine::asserts() +{ + QJSEngine engine; + engine.installExtensions(QJSEngine::ConsoleExtension); + + QTest::ignoreMessage(QtCriticalMsg, "This will fail\n%entry (:1)"); + engine.evaluate("console.assert(0, 'This will fail')"); + + QTest::ignoreMessage(QtCriticalMsg, "This will fail too\n%entry (:1)"); + engine.evaluate("console.assert(1 > 2, 'This will fail too')"); +} + +void tst_QJSEngine::exceptions() +{ + QJSEngine engine; + engine.installExtensions(QJSEngine::ConsoleExtension); + + QTest::ignoreMessage(QtCriticalMsg, "Exception 1\n%entry (:1)"); + engine.evaluate("console.exception('Exception 1')"); +} + +void tst_QJSEngine::installGarbageCollectionFunctions() +{ + QJSEngine engine; + QJSValue global = engine.globalObject(); + QVERIFY(global.property("gc").isUndefined()); + + engine.installExtensions(QJSEngine::GarbageCollectionExtension); + QVERIFY(global.property("gc").isCallable()); +} + +void tst_QJSEngine::installAllExtensions() +{ + QJSEngine engine; + QJSValue global = engine.globalObject(); + // Pick out a few properties from each extension and check that they're there. + QVERIFY(global.property("qsTranslate").isUndefined()); + QVERIFY(global.property("console").isUndefined()); + QVERIFY(global.property("print").isUndefined()); + QVERIFY(global.property("gc").isUndefined()); + + engine.installExtensions(QJSEngine::AllExtensions); + QVERIFY(global.property("qsTranslate").isCallable()); + QVERIFY(global.property("console").isObject()); + QVERIFY(global.property("print").isCallable()); + QVERIFY(global.property("gc").isCallable()); +} + class ObjectWithPrivateMethods : public QObject { Q_OBJECT |