From 6318f91dc0bf1ac428037c963b80b7a5d4e1ad30 Mon Sep 17 00:00:00 2001 From: Kent Hansen Date: Thu, 22 Mar 2012 22:30:14 +0100 Subject: [PATCH] Add internal API for accessing V8 handles of QJS types Make it possible to use the V8 API directly. This might be necessary in cases where the QJS API is missing some functionality (e.g., controlling the V8 profiler), or for performance reasons (e.g., avoiding overhead of QJSValue indirection). The V8 API is clearly more extensive than the QJS API, and QJS will likely never reach feature parity with it (since that's outside the scope of QJS). By providing access to the underlying V8 types, users can still choose to use V8 directly in the (hopefully rare) cases where the public QJS API isn't sufficient. Two new functions are introduced: - qt_QJSEngineV8Context(QJSEngine *) returns a local handle to the engine's internal V8 context. - qt_QJSValueV8Value(const QJSValue &) returns a local handle to the QJSValue's internal V8 value. The caller is responsible for - ensuring that a V8 handle scope is in place; - entering/exiting the QJSEngine's V8 context. The documentation and tests show how that can be done. Also added a benchmark for QJSValue that can be used to measure the effect of using raw V8 API for some common operations. Change-Id: I680aeb2f67ffe5eeadd432a05c8084e43921a118 Reviewed-by: Jamey Hicks Reviewed-by: Lars Knoll --- src/qml/qml/v8/qjsengine.cpp | 29 +++ src/qml/qml/v8/qjsvalue.cpp | 26 +++ src/qml/qml/v8/qjsvalue_impl_p.h | 5 + src/qml/qml/v8/qjsvalue_p.h | 1 + tests/auto/qml/qjsengine/qjsengine.pro | 2 +- tests/auto/qml/qjsengine/tst_qjsengine.cpp | 104 +++++++++++ tests/benchmarks/script/qjsvalue/qjsvalue.pro | 11 ++ tests/benchmarks/script/qjsvalue/tst_qjsvalue.cpp | 189 +++++++++++++++++++++ tests/benchmarks/script/script.pro | 4 + 9 files changed, 370 insertions(+), 1 deletions(-) create mode 100644 tests/benchmarks/script/qjsvalue/qjsvalue.pro create mode 100644 tests/benchmarks/script/qjsvalue/tst_qjsvalue.cpp create mode 100644 tests/benchmarks/script/script.pro diff --git a/src/qml/qml/v8/qjsengine.cpp b/src/qml/qml/v8/qjsengine.cpp index dcdaf2c..03cd51e 100644 --- a/src/qml/qml/v8/qjsengine.cpp +++ b/src/qml/qml/v8/qjsengine.cpp @@ -414,6 +414,35 @@ bool QJSEngine::convertV2(const QJSValue &value, int type, void *ptr) \sa toScriptValue() */ +/*! + \internal + + Returns this engine's internal V8 context. + + The context enables direct use of the V8 API. + The caller is responsible for ensuring that a handle scope is in place, + and for entering/exiting the context. + Example: + + \code + QJSEngine eng; + ... + v8::HandleScope handleScope; + v8::Local context = qt_QJSEngineV8Context(&eng); + v8::Context::Scope contextScope(context); + + // Do stuff (e.g., call v8::Script::Compile()) ... + \endcode + + \sa qt_QJSValueV8Value() +*/ +Q_QML_EXPORT v8::Local qt_QJSEngineV8Context(QJSEngine *engine) +{ + Q_ASSERT(engine != 0); + QV8Engine *d = engine->handle(); + return v8::Local::New(d->context()); +} + QT_END_NAMESPACE #include "moc_qjsengine.cpp" diff --git a/src/qml/qml/v8/qjsvalue.cpp b/src/qml/qml/v8/qjsvalue.cpp index 19aee1e..58732f2 100644 --- a/src/qml/qml/v8/qjsvalue.cpp +++ b/src/qml/qml/v8/qjsvalue.cpp @@ -869,4 +869,30 @@ bool QJSValue::isQObject() const return d->isQObject(); } +/*! + \internal + + Returns this value's internal V8 value, or an empty handle if + the QJSValue isn't bound to a QJSEngine. + + The V8 value enables direct use of the V8 API. + The caller is responsible for ensuring that a handle scope is in place. + Example: + + \code + QJSValue value = ...; + v8::HandleScope handleScope; + v8::Local v8value = qt_QJSValueV8Value(value); + + // Do something with the V8 value (e.g., call v8::Value::IsInt32()) ... + \endcode + + \sa qt_QJSEngineV8Context() +*/ +Q_QML_EXPORT v8::Local qt_QJSValueV8Value(const QJSValue &value) +{ + QJSValuePrivate *d = QJSValuePrivate::get(value); + return v8::Local::New(d->handle()); +} + QT_END_NAMESPACE diff --git a/src/qml/qml/v8/qjsvalue_impl_p.h b/src/qml/qml/v8/qjsvalue_impl_p.h index e6bbd43..d9f78c2 100644 --- a/src/qml/qml/v8/qjsvalue_impl_p.h +++ b/src/qml/qml/v8/qjsvalue_impl_p.h @@ -880,6 +880,11 @@ inline QJSValuePrivate::operator v8::Handle() const return v8::Handle::Cast(m_value); } +inline v8::Handle QJSValuePrivate::handle() const +{ + return m_value; +} + /*! * Return a v8::Handle, assign to the engine if needed. */ diff --git a/src/qml/qml/v8/qjsvalue_p.h b/src/qml/qml/v8/qjsvalue_p.h index 31b30aa..1564b8c 100644 --- a/src/qml/qml/v8/qjsvalue_p.h +++ b/src/qml/qml/v8/qjsvalue_p.h @@ -164,6 +164,7 @@ public: inline operator v8::Handle() const; inline operator v8::Handle() const; + inline v8::Handle handle() const; inline v8::Handle asV8Value(QV8Engine *engine); private: QIntrusiveListNode m_node; diff --git a/tests/auto/qml/qjsengine/qjsengine.pro b/tests/auto/qml/qjsengine/qjsengine.pro index 49338e7..f3d37bc 100644 --- a/tests/auto/qml/qjsengine/qjsengine.pro +++ b/tests/auto/qml/qjsengine/qjsengine.pro @@ -1,7 +1,7 @@ CONFIG += testcase CONFIG += parallel_test TARGET = tst_qjsengine -QT += qml widgets testlib +QT += v8-private qml widgets testlib macx:CONFIG -= app_bundle SOURCES += tst_qjsengine.cpp diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index bf76adb..b0be3be 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -49,6 +49,8 @@ #include #include +#include + Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(QObjectList) @@ -67,6 +69,11 @@ static void collectGarbage_helper(QJSEngine &eng) eng.collectGarbage(); } +QT_BEGIN_NAMESPACE +extern Q_QML_EXPORT v8::Local qt_QJSEngineV8Context(QJSEngine *); +extern Q_QML_EXPORT v8::Local qt_QJSValueV8Value(const QJSValue &); +QT_END_NAMESPACE + class tst_QJSEngine : public QObject { Q_OBJECT @@ -316,6 +323,10 @@ private slots: void dateConversionQtJS(); void functionPrototypeExtensions(); void threadedEngine(); + + void v8Context_simple(); + void v8Context_exception(); + void v8Context_mixAPIs(); }; tst_QJSEngine::tst_QJSEngine() @@ -6351,6 +6362,99 @@ void tst_QJSEngine::threadedEngine() QCOMPARE(thread2.result, 2); } +void tst_QJSEngine::v8Context_simple() +{ + QJSEngine eng; + + v8::HandleScope handleScope; + v8::Local context = QT_PREPEND_NAMESPACE(qt_QJSEngineV8Context(&eng)); + v8::Context::Scope contextScope(context); + + v8::Local script = v8::Script::Compile( + v8::String::New("({ foo: 123, bar: 'ciao', baz: true })")); + + v8::TryCatch tc; + v8::Local result = script->Run(); + + QVERIFY(!tc.HasCaught()); + QVERIFY(result->IsObject()); + + v8::Local object = result.As(); + QVERIFY(object->Get(v8::String::New("foo"))->Equals(v8::Number::New(123))); + QVERIFY(object->Get(v8::String::New("bar"))->Equals(v8::String::New("ciao"))); + QVERIFY(object->Get(v8::String::New("baz"))->IsTrue()); +} + +void tst_QJSEngine::v8Context_exception() +{ + QJSEngine eng; + + v8::HandleScope handleScope; + v8::Local context = qt_QJSEngineV8Context(&eng); + v8::Context::Scope contextScope(context); + + int startLineNumber = 42; + v8::ScriptOrigin origin(v8::String::New("test.js"), v8::Integer::New(startLineNumber)); + v8::Local script = v8::Script::Compile( + v8::String::New( + "function foo(i) {\n" + " if (i > 5)\n" + " throw Error('Catch me if you can');\n" + " foo(i + 1);\n" + "}\n" + "foo(0);"), + &origin); + +// QJS does this for us: +// v8::V8::SetCaptureStackTraceForUncaughtExceptions(true); + + v8::TryCatch tc; + v8::Local result = script->Run(); + + QVERIFY(tc.HasCaught()); + QVERIFY(result.IsEmpty()); + + v8::Local message = tc.Message(); + QVERIFY(!message.IsEmpty()); + QCOMPARE(*v8::String::AsciiValue(message->Get()), "Uncaught Error: Catch me if you can"); + QCOMPARE(*v8::String::AsciiValue(message->GetScriptResourceName()), "test.js"); + QCOMPARE(message->GetLineNumber(), startLineNumber + 3); +} + +void tst_QJSEngine::v8Context_mixAPIs() +{ + QJSEngine eng; + + v8::HandleScope handleScope; + v8::Local context = qt_QJSEngineV8Context(&eng); + v8::Context::Scope contextScope(context); + + QJSValue globalQJS = eng.globalObject(); + v8::Local globalV8Value = qt_QJSValueV8Value(globalQJS); + QVERIFY(!globalV8Value.IsEmpty()); + QVERIFY(globalV8Value->IsObject()); + v8::Local globalV8 = globalV8Value.As(); + + QVERIFY(globalQJS.property("foo").isUndefined()); + QVERIFY(globalV8->Get(v8::String::New("foo"))->IsUndefined()); + + globalQJS.setProperty("foo", 123); + QVERIFY(globalV8->Get(v8::String::New("foo"))->Equals(v8::Number::New(123))); + + globalV8->Set(v8::String::New("bar"), v8::String::New("ciao")); + QVERIFY(globalQJS.property("bar").equals("ciao")); + + QJSValue arrayQJS = eng.newArray(10); + v8::Local arrayV8Value = qt_QJSValueV8Value(arrayQJS); + QVERIFY(!arrayV8Value.IsEmpty()); + QVERIFY(arrayV8Value->IsArray()); + v8::Local arrayV8 = arrayV8Value.As(); + + QCOMPARE(int(arrayV8->Length()), 10); + arrayV8->Set(5, v8::Null()); + QVERIFY(arrayQJS.property(5).isNull()); +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" diff --git a/tests/benchmarks/script/qjsvalue/qjsvalue.pro b/tests/benchmarks/script/qjsvalue/qjsvalue.pro new file mode 100644 index 0000000..eb78cc1 --- /dev/null +++ b/tests/benchmarks/script/qjsvalue/qjsvalue.pro @@ -0,0 +1,11 @@ +CONFIG += testcase +TEMPLATE = app +TARGET = tst_bench_qjsvalue +DEPENDPATH += . +INCLUDEPATH += . +macx:CONFIG -= app_bundle +CONFIG += release + +SOURCES += tst_qjsvalue.cpp + +QT += core-private v8-private qml-private testlib diff --git a/tests/benchmarks/script/qjsvalue/tst_qjsvalue.cpp b/tests/benchmarks/script/qjsvalue/tst_qjsvalue.cpp new file mode 100644 index 0000000..aea74bd --- /dev/null +++ b/tests/benchmarks/script/qjsvalue/tst_qjsvalue.cpp @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +extern Q_QML_EXPORT v8::Local qt_QJSEngineV8Context(QJSEngine *); +extern Q_QML_EXPORT v8::Local qt_QJSValueV8Value(const QJSValue &); +QT_END_NAMESPACE + +class tst_QJSValue : public QObject +{ + Q_OBJECT +public: + tst_QJSValue() {} + +private slots: + void fillArray(); + void fillArray_V8(); + + void property(); + void property_V8(); + + void setProperty(); + void setProperty_V8(); + + void call(); + void call_V8(); +}; + +void tst_QJSValue::fillArray() +{ + QJSEngine eng; + static const int ArrayLength = 10000; + QJSValue array = eng.newArray(ArrayLength); + QBENCHMARK { + for (int i = 0; i < ArrayLength; ++i) + array.setProperty(i, i); + } +} + +void tst_QJSValue::fillArray_V8() +{ + QJSEngine eng; + static const int ArrayLength = 10000; + QJSValue array = eng.newArray(ArrayLength); + + v8::HandleScope handleScope; + v8::Local v8array = qt_QJSValueV8Value(array).As(); + QBENCHMARK { + for (int i = 0; i < ArrayLength; ++i) + v8array->Set(i, v8::Number::New(i)); + } +} + +void tst_QJSValue::property() +{ + QJSEngine eng; + QJSValue object = eng.newObject(); + QString propertyName = QString::fromLatin1("foo"); + object.setProperty(propertyName, 123); + QVERIFY(object.property(propertyName).isNumber()); + QBENCHMARK { + object.property(propertyName); + } +} + +void tst_QJSValue::property_V8() +{ + QJSEngine eng; + QJSValue object = eng.newObject(); + QString propertyName = QString::fromLatin1("foo"); + object.setProperty(propertyName, 123); + QVERIFY(object.property(propertyName).isNumber()); + + v8::HandleScope handleScope; + v8::Local v8object = qt_QJSValueV8Value(object).As(); + v8::Local v8propertyName = v8::String::New("foo"); + QVERIFY(v8object->Get(v8propertyName)->IsNumber()); + QBENCHMARK { + v8object->Get(v8propertyName); + } +} + +void tst_QJSValue::setProperty() +{ + QJSEngine eng; + QJSValue object = eng.newObject(); + QString propertyName = QString::fromLatin1("foo"); + QJSValue value(123); + QBENCHMARK { + object.setProperty(propertyName, value); + } +} + +void tst_QJSValue::setProperty_V8() +{ + QJSEngine eng; + QJSValue object = eng.newObject(); + + v8::HandleScope handleScope; + // A context scope is needed for v8::Object::Set(), otherwise we crash. + v8::Local context = qt_QJSEngineV8Context(&eng); + v8::Context::Scope contextScope(context); + + v8::Local v8object = qt_QJSValueV8Value(object).As(); + v8::Local v8propertyName = v8::String::New("foo"); + v8::Local v8value = v8::Number::New(123); + QBENCHMARK { + v8object->Set(v8propertyName, v8value); + } +} + +#define TEST_FUNCTION_SOURCE "(function() { return 123; })" + +void tst_QJSValue::call() +{ + QJSEngine eng; + QJSValue fun = eng.evaluate(TEST_FUNCTION_SOURCE); + QVERIFY(fun.isCallable()); + QJSValueList args; + QVERIFY(fun.call(args).isNumber()); + QBENCHMARK { + fun.call(args); + } +} + +void tst_QJSValue::call_V8() +{ + QJSEngine eng; + QJSValue fun = eng.evaluate(TEST_FUNCTION_SOURCE); + QVERIFY(fun.isCallable()); + + v8::HandleScope handleScope; + v8::Local context = qt_QJSEngineV8Context(&eng); + v8::Context::Scope contextScope(context); + + v8::Local v8fun = qt_QJSValueV8Value(fun).As(); + v8::Local v8thisObject = v8::Object::New(); + QVERIFY(v8fun->Call(v8thisObject, /*argc=*/0, /*argv=*/0)->IsNumber()); + QBENCHMARK { + v8fun->Call(v8thisObject, /*argc=*/0, /*argv=*/0); + } +} + +QTEST_MAIN(tst_QJSValue) + +#include "tst_qjsvalue.moc" diff --git a/tests/benchmarks/script/script.pro b/tests/benchmarks/script/script.pro new file mode 100644 index 0000000..37dc038 --- /dev/null +++ b/tests/benchmarks/script/script.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + qjsvalue -- 1.7.2.5