}
}
- // ensure we clean up QObjects with JS ownership
- d->v8engine()->gc();
-
if (d->incubationController)
d->incubationController->d = 0;
}
\value JavaScriptOwnership The object is owned by JavaScript.
When the object is returned to QML as the return value of a method
- call or property access, QML will delete the object if there are no
- remaining JavaScript references to it and it has no
- QObject::parent(). This option is similar to
- QScriptEngine::ScriptOwnership.
+ call or property access, QML will track it, and delete the object
+ if there are no remaining JavaScript references to it and it has no
+ QObject::parent(). An object tracked by one QQmlEngine
+ will be deleted during that QQmlEngine's destructor, and thus
+ JavaScript references between objects with JavaScriptOwnership from
+ two different engines will not be valid after the deletion of one of
+ those engines. This option is similar to QScriptEngine::ScriptOwnership.
Generally an application doesn't need to set an object's ownership
explicitly. QML uses a heuristic to set the default object
#include <private/qqmlpropertycache_p.h>
+#include "qv8objectresource_p.h"
#include "qv8contextwrapper_p.h"
#include "qv8qobjectwrapper_p.h"
#include "qv8stringwrapper_p.h"
// a handle, qFatal() is called.
// #define QML_GLOBAL_HANDLE_DEBUGGING
-#define V8_RESOURCE_TYPE(resourcetype) \
-public: \
- enum { V8ResourceType = QV8ObjectResource:: resourcetype }; \
- virtual QV8ObjectResource::ResourceType resourceType() const { return QV8ObjectResource:: resourcetype; } \
-private:
-
#define V8ENGINE() ((QV8Engine *)v8::External::Unwrap(args.Data()))
#define V8FUNCTION(function, engine) v8::FunctionTemplate::New(function, v8::External::Wrap((QV8Engine*)engine))->GetFunction()
#define V8THROW_ERROR(string) { \
} \
-class QV8Engine;
-class QV8ObjectResource : public v8::Object::ExternalResource
-{
-public:
- QV8ObjectResource(QV8Engine *engine) : engine(engine) { Q_ASSERT(engine); }
- enum ResourceType { ContextType, QObjectType, TypeType, ListType, VariantType,
- ValueTypeType, XMLHttpRequestType, DOMNodeType, SQLDatabaseType,
- ListModelType, Context2DType, Context2DStyleType, Context2DPixelArrayType,
- ParticleDataType, SignalHandlerType, IncubatorType, VisualDataItemType,
- SequenceType, LocaleDataType };
- virtual ResourceType resourceType() const = 0;
-
- QV8Engine *engine;
-};
-
template<class T>
inline T *v8_resource_cast(v8::Handle<v8::Object> object) {
QV8ObjectResource *resource = static_cast<QV8ObjectResource *>(object->GetExternalResource());
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtQml module 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$
+**
+****************************************************************************/
+
+#ifndef QV8OBJECTRESOURCE_P_H
+#define QV8OBJECTRESOURCE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+#include <private/qv8_p.h>
+
+QT_BEGIN_NAMESPACE
+
+#define V8_RESOURCE_TYPE(resourcetype) \
+public: \
+ enum { V8ResourceType = QV8ObjectResource:: resourcetype }; \
+ virtual QV8ObjectResource::ResourceType resourceType() const { return QV8ObjectResource:: resourcetype; } \
+private:
+
+class QV8Engine;
+class QV8ObjectResource : public v8::Object::ExternalResource
+{
+public:
+ QV8ObjectResource(QV8Engine *engine) : engine(engine) { Q_ASSERT(engine); }
+ enum ResourceType { ContextType, QObjectType, TypeType, ListType, VariantType,
+ ValueTypeType, XMLHttpRequestType, DOMNodeType, SQLDatabaseType,
+ ListModelType, Context2DType, Context2DStyleType, Context2DPixelArrayType,
+ ParticleDataType, SignalHandlerType, IncubatorType, VisualDataItemType,
+ SequenceType, LocaleDataType };
+ virtual ResourceType resourceType() const = 0;
+
+ QV8Engine *engine;
+};
+
+QT_END_NAMESPACE
+
+#endif // QV8OBJECTRESOURCE_P_H
// XXX TODO: Need to review all calls to QQmlEngine *engine() to confirm QObjects work
// correctly in a worker thread
-class QV8QObjectResource : public QV8ObjectResource
-{
- V8_RESOURCE_TYPE(QObjectType);
-
-public:
- QV8QObjectResource(QV8Engine *engine, QObject *object);
-
- QQmlGuard<QObject> object;
-};
-
class QV8QObjectInstance : public QQmlGuard<QObject>
{
public:
qPersistentDispose(m_signalHandlerConstructor);
qPersistentDispose(m_methodConstructor);
qPersistentDispose(m_constructor);
+
+ QIntrusiveList<QV8QObjectResource, &QV8QObjectResource::weakResource>::iterator i = m_javaScriptOwnedWeakQObjects.begin();
+ for (; i != m_javaScriptOwnedWeakQObjects.end(); ++i) {
+ QV8QObjectResource *resource = *i;
+ Q_ASSERT(resource);
+ deleteWeakQObject(resource);
+ }
}
struct ReadAccessor {
v8::ThrowException(v8::Exception::Error(v8engine->toString(error)));
}
-static void WeakQObjectReferenceCallback(v8::Persistent<v8::Value> handle, void *)
+void QV8QObjectWrapper::WeakQObjectReferenceCallback(v8::Persistent<v8::Value> handle, void *wrapper)
{
Q_ASSERT(handle->IsObject());
-
QV8QObjectResource *resource = v8_resource_check<QV8QObjectResource>(handle->ToObject());
-
Q_ASSERT(resource);
- QObject *object = resource->object;
- if (object) {
- QQmlData *ddata = QQmlData::get(object, false);
- if (ddata) {
- ddata->v8object.Clear();
- if (!object->parent() && !ddata->indestructible) {
- ddata->isQueuedForDeletion = true;
- object->deleteLater();
- }
- }
- }
+ static_cast<QV8QObjectWrapper*>(wrapper)->unregisterWeakQObjectReference(resource);
+ static_cast<QV8QObjectWrapper*>(wrapper)->deleteWeakQObject(resource);
qPersistentDispose(handle);
}
v8::Local<v8::Object> rv = newQObject(object, ddata, m_engine);
ddata->v8object = qPersistentNew<v8::Object>(rv);
- ddata->v8object.MakeWeak(0, WeakQObjectReferenceCallback);
+ ddata->v8object.MakeWeak(this, WeakQObjectReferenceCallback);
ddata->v8objectid = m_id;
+ QV8QObjectResource *resource = v8_resource_check<QV8QObjectResource>(rv);
+ registerWeakQObjectReference(resource);
return rv;
} else {
if ((!found || (*iter)->v8object.IsEmpty()) && ddata->v8object.IsEmpty()) {
v8::Local<v8::Object> rv = newQObject(object, ddata, m_engine);
ddata->v8object = qPersistentNew<v8::Object>(rv);
- ddata->v8object.MakeWeak(0, WeakQObjectReferenceCallback);
+ ddata->v8object.MakeWeak(this, WeakQObjectReferenceCallback);
ddata->v8objectid = m_id;
+ QV8QObjectResource *resource = v8_resource_check<QV8QObjectResource>(rv);
+ registerWeakQObjectReference(resource);
if (found) {
delete (*iter);
return v8::Local<v8::Object>::New((*iter)->v8object);
}
}
+void QV8QObjectWrapper::deleteWeakQObject(QV8QObjectResource *resource)
+{
+ QObject *object = resource->object;
+ if (object) {
+ QQmlData *ddata = QQmlData::get(object, false);
+ if (ddata) {
+ ddata->v8object.Clear();
+ if (!object->parent() && !ddata->indestructible) {
+ ddata->isQueuedForDeletion = true;
+ object->deleteLater();
+ }
+ }
+ }
+}
QPair<QObject *, int> QV8QObjectWrapper::ExtractQtSignal(QV8Engine *engine, v8::Handle<v8::Object> object)
{
#include <private/qhashedstring_p.h>
#include <private/qqmldata_p.h>
#include <private/qqmlpropertycache_p.h>
+#include <private/qintrusivelist_p.h>
+#include "qv8objectresource_p.h"
QT_BEGIN_NAMESPACE
class QV8QObjectInstance;
class QV8QObjectConnectionList;
class QQmlPropertyCache;
+
+class QV8QObjectResource : public QV8ObjectResource
+{
+ V8_RESOURCE_TYPE(QObjectType);
+
+public:
+ QV8QObjectResource(QV8Engine *engine, QObject *object);
+
+ QQmlGuard<QObject> object;
+ QIntrusiveListNode weakResource;
+};
+
class Q_QML_EXPORT QV8QObjectWrapper
{
public:
inline v8::Handle<v8::Value> getProperty(QObject *, const QHashedV8String &, RevisionMode);
inline bool setProperty(QObject *, const QHashedV8String &, v8::Handle<v8::Value>, RevisionMode);
+ void registerWeakQObjectReference(QV8QObjectResource *resource)
+ {
+ m_javaScriptOwnedWeakQObjects.insert(resource);
+ }
+
+ void unregisterWeakQObjectReference(QV8QObjectResource *resource)
+ {
+ m_javaScriptOwnedWeakQObjects.remove(resource);
+ }
+
private:
friend class QQmlPropertyCache;
friend class QV8QObjectConnectionList;
friend class QV8QObjectInstance;
v8::Local<v8::Object> newQObject(QObject *, QQmlData *, QV8Engine *);
+ void deleteWeakQObject(QV8QObjectResource *resource);
static v8::Handle<v8::Value> GetProperty(QV8Engine *, QObject *, v8::Handle<v8::Value> *,
const QHashedV8String &, QV8QObjectWrapper::RevisionMode);
static bool SetProperty(QV8Engine *, QObject *, const QHashedV8String &,
static v8::Handle<v8::Value> Invoke(const v8::Arguments &args);
static QPair<QObject *, int> ExtractQtMethod(QV8Engine *, v8::Handle<v8::Function>);
static QPair<QObject *, int> ExtractQtSignal(QV8Engine *, v8::Handle<v8::Object>);
+ static void WeakQObjectReferenceCallback(v8::Persistent<v8::Value> handle, void *wrapper);
QV8Engine *m_engine;
quint32 m_id;
QHash<QObject *, QV8QObjectConnectionList *> m_connections;
typedef QHash<QObject *, QV8QObjectInstance *> TaintedHash;
TaintedHash m_taintedObjects;
+ QIntrusiveList<QV8QObjectResource, &QV8QObjectResource::weakResource> m_javaScriptOwnedWeakQObjects;
};
v8::Handle<v8::Value> QV8QObjectWrapper::getProperty(QObject *object, const QHashedV8String &string,
$$PWD/qv8engine_impl_p.h \
$$PWD/qv8domerrors_p.h \
$$PWD/qv8sqlerrors_p.h \
- $$PWD/qqmlbuiltinfunctions_p.h
+ $$PWD/qqmlbuiltinfunctions_p.h \
+ $$PWD/qv8objectresource_p.h
SOURCES += \
$$PWD/qv8stringwrapper.cpp \
#endif
void newQObject_promoteNonObject();
void newQObject_promoteNonQScriptObject();
+ void newQObject_deletedEngine();
#if 0 // ### FIXME: No QScript Metaobject support right now
void newQMetaObject();
void newActivationObject();
#endif
}
+void tst_QJSEngine::newQObject_deletedEngine()
+{
+ QJSValue object;
+ QObject *ptr = new QObject();
+ QSignalSpy spy(ptr, SIGNAL(destroyed()));
+ {
+ QJSEngine engine;
+ object = engine.newQObject(ptr);
+ engine.globalObject().setProperty("obj", object);
+ }
+ QTRY_VERIFY(spy.count());
+}
+
#if 0 // ### FIXME: No QScript Metaobject support right now
QT_BEGIN_NAMESPACE
Q_SCRIPT_DECLARE_QMETAOBJECT(QObject, QObject*)
QQmlEngine::setObjectOwnership(second1, QQmlEngine::JavaScriptOwnership);
QQmlEngine::setObjectOwnership(first2, QQmlEngine::JavaScriptOwnership);
QQmlEngine::setObjectOwnership(second2, QQmlEngine::JavaScriptOwnership);
- gc(engine);
- QCOMPARE(dtorCount, 0);
- delete hrmEngine2;
- gc(engine);
+ gc(*hrmEngine1);
+ gc(*hrmEngine2);
QCOMPARE(dtorCount, 0);
+ delete hrmEngine2; // should trigger deletion of objects with JS ownership tracked by this engine
+ gc(*hrmEngine1);
+ QCOMPARE(dtorCount, 2); // first2 and second2 should have been deleted.
delete object1;
delete object2;
- hrmEngine1->collectGarbage();
+ gc(*hrmEngine1);
+ QCOMPARE(dtorCount, 6); // deleting object1 and object2 should trigger deletion of first1 and first2.
+ delete hrmEngine1;
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
QCoreApplication::processEvents();
- QCOMPARE(dtorCount, 6);
- delete hrmEngine1;
+ QCOMPARE(dtorCount, 6); // all objects should have been cleaned up prior to deleting hrmEngine1.
}
}
#include <QPointer>
#include <QDir>
#include <QStandardPaths>
+#include <QSignalSpy>
#include <QDebug>
#include <QQmlComponent>
#include <QQmlNetworkAccessManagerFactory>
void outputWarningsToStandardError();
void objectOwnership();
void multipleEngines();
+
+public slots:
+ QObject *createAQObjectForOwnershipTest ()
+ {
+ static QObject *ptr = new QObject();
+ return ptr;
+ }
};
void tst_qqmlengine::rootContext()
delete o;
}
-
+ {
+ QObject *ptr = createAQObjectForOwnershipTest();
+ QSignalSpy spy(ptr, SIGNAL(destroyed()));
+ {
+ QQmlEngine engine;
+ QQmlComponent c(&engine);
+ engine.rootContext()->setContextProperty("test", this);
+ QQmlEngine::setObjectOwnership(ptr, QQmlEngine::JavaScriptOwnership);
+ c.setData("import QtQuick 2.0; Item { property int data: test.createAQObjectForOwnershipTest() ? 0 : 1 }", QUrl());
+ QVERIFY(c.isReady());
+ QObject *o = c.create();
+ QVERIFY(o != 0);
+ }
+ QTRY_VERIFY(spy.count());
+ }
+ {
+ QObject *ptr = new QObject();
+ QSignalSpy spy(ptr, SIGNAL(destroyed()));
+ {
+ QQmlEngine engine;
+ QQmlComponent c(&engine);
+ engine.rootContext()->setContextProperty("test", ptr);
+ QQmlEngine::setObjectOwnership(ptr, QQmlEngine::JavaScriptOwnership);
+ c.setData("import QtQuick 2.0; QtObject { property var object: { var i = test; test ? 0 : 1 } }", QUrl());
+ QVERIFY(c.isReady());
+ QObject *o = c.create();
+ QVERIFY(o != 0);
+ engine.rootContext()->setContextProperty("test", 0);
+ }
+ QTRY_VERIFY(spy.count());
+ }
}
// Test an object can be accessed by multiple engines