diff options
Diffstat (limited to 'src/qml/debugger')
30 files changed, 7050 insertions, 0 deletions
diff --git a/src/qml/debugger/debugger.pri b/src/qml/debugger/debugger.pri new file mode 100644 index 0000000000..10ca9706c4 --- /dev/null +++ b/src/qml/debugger/debugger.pri @@ -0,0 +1,32 @@ +SOURCES += \ + $$PWD/qpacketprotocol.cpp \ + $$PWD/qqmldebugservice.cpp \ + $$PWD/qqmldebugclient.cpp \ + $$PWD/qqmlenginedebug.cpp \ + $$PWD/qqmlprofilerservice.cpp \ + $$PWD/qqmldebughelper.cpp \ + $$PWD/qqmldebugserver.cpp \ + $$PWD/qqmlinspectorservice.cpp \ + $$PWD/qv8debugservice.cpp \ + $$PWD/qv8profilerservice.cpp \ + $$PWD/qqmlenginedebugservice.cpp \ + $$PWD/qdebugmessageservice.cpp + +HEADERS += \ + $$PWD/qpacketprotocol_p.h \ + $$PWD/qqmldebugservice_p.h \ + $$PWD/qqmldebugservice_p_p.h \ + $$PWD/qqmldebugclient_p.h \ + $$PWD/qqmlenginedebug_p.h \ + $$PWD/qqmlprofilerservice_p.h \ + $$PWD/qqmldebughelper_p.h \ + $$PWD/qqmldebugserver_p.h \ + $$PWD/qqmldebugserverconnection_p.h \ + $$PWD/qqmldebugstatesdelegate_p.h \ + $$PWD/qqmlinspectorservice_p.h \ + $$PWD/qqmlinspectorinterface_p.h \ + $$PWD/qv8debugservice_p.h \ + $$PWD/qv8profilerservice_p.h \ + $$PWD/qqmlenginedebugservice_p.h \ + $$PWD/qqmldebug.h \ + $$PWD/qdebugmessageservice_p.h diff --git a/src/qml/debugger/qdebugmessageservice.cpp b/src/qml/debugger/qdebugmessageservice.cpp new file mode 100644 index 0000000000..2c52809e56 --- /dev/null +++ b/src/qml/debugger/qdebugmessageservice.cpp @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qdebugmessageservice_p.h" +#include "qqmldebugservice_p_p.h" + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QDebugMessageService, qmlDebugMessageService) + +void DebugMessageHandler(QtMsgType type, const QMessageLogContext &ctxt, + const char *buf) +{ + QDebugMessageService::instance()->sendDebugMessage(type, ctxt, buf); +} + +class QDebugMessageServicePrivate : public QQmlDebugServicePrivate +{ +public: + QDebugMessageServicePrivate() + : oldMsgHandler(0) + , prevState(QQmlDebugService::NotConnected) + { + } + + QMessageHandler oldMsgHandler; + QQmlDebugService::State prevState; +}; + +QDebugMessageService::QDebugMessageService(QObject *parent) : + QQmlDebugService(*(new QDebugMessageServicePrivate()), + QLatin1String("DebugMessages"), 2, parent) +{ + Q_D(QDebugMessageService); + + registerService(); + if (state() == Enabled) { + d->oldMsgHandler = qInstallMessageHandler(DebugMessageHandler); + d->prevState = Enabled; + } +} + +QDebugMessageService *QDebugMessageService::instance() +{ + return qmlDebugMessageService(); +} + +void QDebugMessageService::sendDebugMessage(QtMsgType type, + const QMessageLogContext &ctxt, + const char *buf) +{ + Q_D(QDebugMessageService); + + //We do not want to alter the message handling mechanism + //We just eavesdrop and forward the messages to a port + //only if a client is connected to it. + QByteArray message; + QDataStream ws(&message, QIODevice::WriteOnly); + ws << QByteArray("MESSAGE") << type << QString::fromLocal8Bit(buf).toUtf8(); + ws << QString::fromLatin1(ctxt.file).toUtf8(); + ws << ctxt.line << QString::fromLatin1(ctxt.function).toUtf8(); + + sendMessage(message); + if (d->oldMsgHandler) + (*d->oldMsgHandler)(type, ctxt, buf); +} + +void QDebugMessageService::stateChanged(State state) +{ + Q_D(QDebugMessageService); + + if (state != Enabled && d->prevState == Enabled) { + QMessageHandler handler = qInstallMessageHandler(d->oldMsgHandler); + // has our handler been overwritten in between? + if (handler != DebugMessageHandler) + qInstallMessageHandler(handler); + + } else if (state == Enabled && d->prevState != Enabled) { + d->oldMsgHandler = qInstallMessageHandler(DebugMessageHandler); + + } + + d->prevState = state; +} + +QT_END_NAMESPACE diff --git a/src/qml/debugger/qdebugmessageservice_p.h b/src/qml/debugger/qdebugmessageservice_p.h new file mode 100644 index 0000000000..88b918e217 --- /dev/null +++ b/src/qml/debugger/qdebugmessageservice_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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 QDEBUGMESSAGESERVICE_P_H +#define QDEBUGMESSAGESERVICE_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 "qqmldebugservice_p.h" + +#include <QtCore/qlogging.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qml) + +class QDebugMessageServicePrivate; + +class QDebugMessageService : public QQmlDebugService +{ + Q_OBJECT +public: + QDebugMessageService(QObject *parent = 0); + + static QDebugMessageService *instance(); + + void sendDebugMessage(QtMsgType type, const QMessageLogContext &ctxt, + const char *buf); + +protected: + void stateChanged(State); + +private: + Q_DISABLE_COPY(QDebugMessageService) + Q_DECLARE_PRIVATE(QDebugMessageService) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QDEBUGMESSAGESERVICE_P_H diff --git a/src/qml/debugger/qpacketprotocol.cpp b/src/qml/debugger/qpacketprotocol.cpp new file mode 100644 index 0000000000..978054a238 --- /dev/null +++ b/src/qml/debugger/qpacketprotocol.cpp @@ -0,0 +1,550 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qpacketprotocol_p.h" + +#include <QtCore/QBuffer> +#include <QtCore/QElapsedTimer> + +QT_BEGIN_NAMESPACE + +static const unsigned int MAX_PACKET_SIZE = 0x7FFFFFFF; + +/*! + \class QPacketProtocol + \internal + + \brief The QPacketProtocol class encapsulates communicating discrete packets + across fragmented IO channels, such as TCP sockets. + + QPacketProtocol makes it simple to send arbitrary sized data "packets" across + fragmented transports such as TCP and UDP. + + As transmission boundaries are not respected, sending packets over protocols + like TCP frequently involves "stitching" them back together at the receiver. + QPacketProtocol makes this easier by performing this task for you. Packet + data sent using QPacketProtocol is prepended with a 4-byte size header + allowing the receiving QPacketProtocol to buffer the packet internally until + it has all been received. QPacketProtocol does not perform any sanity + checking on the size or on the data, so this class should only be used in + prototyping or trusted situations where DOS attacks are unlikely. + + QPacketProtocol does not perform any communications itself. Instead it can + operate on any QIODevice that supports the QIODevice::readyRead() signal. A + logical "packet" is encapsulated by the companion QPacket class. The + following example shows two ways to send data using QPacketProtocol. The + transmitted data is equivalent in both. + + \code + QTcpSocket socket; + // ... connect socket ... + + QPacketProtocol protocol(&socket); + + // Send packet the quick way + protocol.send() << "Hello world" << 123; + + // Send packet the longer way + QPacket packet; + packet << "Hello world" << 123; + protocol.send(packet); + \endcode + + Likewise, the following shows how to read data from QPacketProtocol, assuming + that the QPacketProtocol::readyRead() signal has been emitted. + + \code + // ... QPacketProtocol::readyRead() is emitted ... + + int a; + QByteArray b; + + // Receive packet the quick way + protocol.read() >> a >> b; + + // Receive packet the longer way + QPacket packet = protocol.read(); + p >> a >> b; + \endcode + + \ingroup io + \sa QPacket +*/ + +class QPacketProtocolPrivate : public QObject +{ + Q_OBJECT +public: + QPacketProtocolPrivate(QPacketProtocol *parent, QIODevice *_dev) + : QObject(parent), inProgressSize(-1), maxPacketSize(MAX_PACKET_SIZE), + waitingForPacket(false), dev(_dev) + { + Q_ASSERT(4 == sizeof(qint32)); + + QObject::connect(this, SIGNAL(readyRead()), + parent, SIGNAL(readyRead())); + QObject::connect(this, SIGNAL(packetWritten()), + parent, SIGNAL(packetWritten())); + QObject::connect(this, SIGNAL(invalidPacket()), + parent, SIGNAL(invalidPacket())); + QObject::connect(dev, SIGNAL(readyRead()), + this, SLOT(readyToRead())); + QObject::connect(dev, SIGNAL(aboutToClose()), + this, SLOT(aboutToClose())); + QObject::connect(dev, SIGNAL(bytesWritten(qint64)), + this, SLOT(bytesWritten(qint64))); + } + +Q_SIGNALS: + void readyRead(); + void packetWritten(); + void invalidPacket(); + +public Q_SLOTS: + void aboutToClose() + { + inProgress.clear(); + sendingPackets.clear(); + inProgressSize = -1; + } + + void bytesWritten(qint64 bytes) + { + Q_ASSERT(!sendingPackets.isEmpty()); + + while (bytes) { + if (sendingPackets.at(0) > bytes) { + sendingPackets[0] -= bytes; + bytes = 0; + } else { + bytes -= sendingPackets.at(0); + sendingPackets.removeFirst(); + emit packetWritten(); + } + } + } + + void readyToRead() + { + while (true) { + // Need to get trailing data + if (-1 == inProgressSize) { + // We need a size header of sizeof(qint32) + if (sizeof(qint32) > (uint)dev->bytesAvailable()) + return; + + // Read size header + int read = dev->read((char *)&inProgressSize, sizeof(qint32)); + Q_ASSERT(read == sizeof(qint32)); + Q_UNUSED(read); + + // Check sizing constraints + if (inProgressSize > maxPacketSize) { + QObject::disconnect(dev, SIGNAL(readyRead()), + this, SLOT(readyToRead())); + QObject::disconnect(dev, SIGNAL(aboutToClose()), + this, SLOT(aboutToClose())); + QObject::disconnect(dev, SIGNAL(bytesWritten(qint64)), + this, SLOT(bytesWritten(qint64))); + dev = 0; + emit invalidPacket(); + return; + } + + inProgressSize -= sizeof(qint32); + } else { + inProgress.append(dev->read(inProgressSize - inProgress.size())); + + if (inProgressSize == inProgress.size()) { + // Packet has arrived! + packets.append(inProgress); + inProgressSize = -1; + inProgress.clear(); + + waitingForPacket = false; + emit readyRead(); + } else + return; + } + } + } + +public: + QList<qint64> sendingPackets; + QList<QByteArray> packets; + QByteArray inProgress; + qint32 inProgressSize; + qint32 maxPacketSize; + bool waitingForPacket; + QIODevice *dev; +}; + +/*! + Construct a QPacketProtocol instance that works on \a dev with the + specified \a parent. + */ +QPacketProtocol::QPacketProtocol(QIODevice *dev, QObject *parent) + : QObject(parent), d(new QPacketProtocolPrivate(this, dev)) +{ + Q_ASSERT(dev); +} + +/*! + Destroys the QPacketProtocol instance. + */ +QPacketProtocol::~QPacketProtocol() +{ +} + +/*! + Returns the maximum packet size allowed. By default this is + 2,147,483,647 bytes. + + If a packet claiming to be larger than the maximum packet size is received, + the QPacketProtocol::invalidPacket() signal is emitted. + + \sa QPacketProtocol::setMaximumPacketSize() + */ +qint32 QPacketProtocol::maximumPacketSize() const +{ + return d->maxPacketSize; +} + +/*! + Sets the maximum allowable packet size to \a max. + + \sa QPacketProtocol::maximumPacketSize() + */ +qint32 QPacketProtocol::setMaximumPacketSize(qint32 max) +{ + if (max > (signed)sizeof(qint32)) + d->maxPacketSize = max; + return d->maxPacketSize; +} + +/*! + Returns a streamable object that is transmitted on destruction. For example + + \code + protocol.send() << "Hello world" << 123; + \endcode + + will send a packet containing "Hello world" and 123. To construct more + complex packets, explicitly construct a QPacket instance. + */ +QPacketAutoSend QPacketProtocol::send() +{ + return QPacketAutoSend(this); +} + +/*! + \fn void QPacketProtocol::send(const QPacket & packet) + + Transmit the \a packet. + */ +void QPacketProtocol::send(const QPacket & p) +{ + if (p.b.isEmpty()) + return; // We don't send empty packets + + qint64 sendSize = p.b.size() + sizeof(qint32); + + d->sendingPackets.append(sendSize); + qint32 sendSize32 = sendSize; + qint64 writeBytes = d->dev->write((char *)&sendSize32, sizeof(qint32)); + Q_ASSERT(writeBytes == sizeof(qint32)); + writeBytes = d->dev->write(p.b); + Q_ASSERT(writeBytes == p.b.size()); +} + +/*! + Returns the number of received packets yet to be read. + */ +qint64 QPacketProtocol::packetsAvailable() const +{ + return d->packets.count(); +} + +/*! + Discard any unread packets. + */ +void QPacketProtocol::clear() +{ + d->packets.clear(); +} + +/*! + Return the next unread packet, or an invalid QPacket instance if no packets + are available. This method does NOT block. + */ +QPacket QPacketProtocol::read() +{ + if (0 == d->packets.count()) + return QPacket(); + + QPacket rv(d->packets.at(0)); + d->packets.removeFirst(); + return rv; +} + +/* + Returns the difference between msecs and elapsed. If msecs is -1, + however, -1 is returned. +*/ +static int qt_timeout_value(int msecs, int elapsed) +{ + if (msecs == -1) + return -1; + + int timeout = msecs - elapsed; + return timeout < 0 ? 0 : timeout; +} + +/*! + This function locks until a new packet is available for reading and the + \l{QIODevice::}{readyRead()} signal has been emitted. The function + will timeout after \a msecs milliseconds; the default timeout is + 30000 milliseconds. + + The function returns true if the readyRead() signal is emitted and + there is new data available for reading; otherwise it returns false + (if an error occurred or the operation timed out). + */ + +bool QPacketProtocol::waitForReadyRead(int msecs) +{ + if (!d->packets.isEmpty()) + return true; + + QElapsedTimer stopWatch; + stopWatch.start(); + + d->waitingForPacket = true; + do { + if (!d->dev->waitForReadyRead(msecs)) + return false; + if (!d->waitingForPacket) + return true; + msecs = qt_timeout_value(msecs, stopWatch.elapsed()); + } while (true); +} + +/*! + Return the QIODevice passed to the QPacketProtocol constructor. +*/ +QIODevice *QPacketProtocol::device() +{ + return d->dev; +} + +/*! + \fn void QPacketProtocol::readyRead() + + Emitted whenever a new packet is received. Applications may use + QPacketProtocol::read() to retrieve this packet. + */ + +/*! + \fn void QPacketProtocol::invalidPacket() + + A packet larger than the maximum allowable packet size was received. The + packet will be discarded and, as it indicates corruption in the protocol, no + further packets will be received. + */ + +/*! + \fn void QPacketProtocol::packetWritten() + + Emitted each time a packet is completing written to the device. This signal + may be used for communications flow control. + */ + +/*! + \class QPacket + \internal + + \brief The QPacket class encapsulates an unfragmentable packet of data to be + transmitted by QPacketProtocol. + + The QPacket class works together with QPacketProtocol to make it simple to + send arbitrary sized data "packets" across fragmented transports such as TCP + and UDP. + + QPacket provides a QDataStream interface to an unfragmentable packet. + Applications should construct a QPacket, propagate it with data and then + transmit it over a QPacketProtocol instance. For example: + \code + QPacketProtocol protocol(...); + + QPacket myPacket; + myPacket << "Hello world!" << 123; + protocol.send(myPacket); + \endcode + + As long as both ends of the connection are using the QPacketProtocol class, + the data within this packet will be delivered unfragmented at the other end, + ready for extraction. + + \code + QByteArray greeting; + int count; + + QPacket myPacket = protocol.read(); + + myPacket >> greeting >> count; + \endcode + + Only packets returned from QPacketProtocol::read() may be read from. QPacket + instances constructed by directly by applications are for transmission only + and are considered "write only". Attempting to read data from them will + result in undefined behavior. + + \ingroup io + \sa QPacketProtocol + */ + +/*! + Constructs an empty write-only packet. + */ +QPacket::QPacket() + : QDataStream(), buf(0) +{ + buf = new QBuffer(&b); + buf->open(QIODevice::WriteOnly); + setDevice(buf); + setVersion(QDataStream::Qt_4_7); +} + +/*! + Destroys the QPacket instance. + */ +QPacket::~QPacket() +{ + if (buf) { + delete buf; + buf = 0; + } +} + +/*! + Creates a copy of \a other. The initial stream positions are shared, but the + two packets are otherwise independent. + */ +QPacket::QPacket(const QPacket & other) + : QDataStream(), b(other.b), buf(0) +{ + buf = new QBuffer(&b); + buf->open(other.buf->openMode()); + setDevice(buf); +} + +/*! + \internal + */ +QPacket::QPacket(const QByteArray & ba) + : QDataStream(), b(ba), buf(0) +{ + buf = new QBuffer(&b); + buf->open(QIODevice::ReadOnly); + setDevice(buf); +} + +/*! + Returns true if this packet is empty - that is, contains no data. + */ +bool QPacket::isEmpty() const +{ + return b.isEmpty(); +} + +/*! + Returns raw packet data. + */ +QByteArray QPacket::data() const +{ + return b; +} + +/*! + Clears data in the packet. This is useful for reusing one writable packet. + For example + \code + QPacketProtocol protocol(...); + + QPacket packet; + + packet << "Hello world!" << 123; + protocol.send(packet); + + packet.clear(); + packet << "Goodbyte world!" << 789; + protocol.send(packet); + \endcode + */ +void QPacket::clear() +{ + QBuffer::OpenMode oldMode = buf->openMode(); + buf->close(); + b.clear(); + buf->setBuffer(&b); // reset QBuffer internals with new size of b. + buf->open(oldMode); +} + +/*! + \class QPacketAutoSend + \internal + + \internal + */ +QPacketAutoSend::QPacketAutoSend(QPacketProtocol *_p) + : QPacket(), p(_p) +{ +} + +QPacketAutoSend::~QPacketAutoSend() +{ + if (!b.isEmpty()) + p->send(*this); +} + +QT_END_NAMESPACE + +#include <qpacketprotocol.moc> diff --git a/src/qml/debugger/qpacketprotocol_p.h b/src/qml/debugger/qpacketprotocol_p.h new file mode 100644 index 0000000000..c6123d2836 --- /dev/null +++ b/src/qml/debugger/qpacketprotocol_p.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** 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 QPACKETPROTOCOL_H +#define QPACKETPROTOCOL_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/qobject.h> +#include <QtCore/qdatastream.h> + +#include <private/qtqmlglobal_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + + +class QIODevice; +class QBuffer; +class QPacket; +class QPacketAutoSend; +class QPacketProtocolPrivate; + +class Q_QML_PRIVATE_EXPORT QPacketProtocol : public QObject +{ + Q_OBJECT +public: + explicit QPacketProtocol(QIODevice *dev, QObject *parent = 0); + virtual ~QPacketProtocol(); + + qint32 maximumPacketSize() const; + qint32 setMaximumPacketSize(qint32); + + QPacketAutoSend send(); + void send(const QPacket &); + + qint64 packetsAvailable() const; + QPacket read(); + + bool waitForReadyRead(int msecs = 3000); + + void clear(); + + QIODevice *device(); + +Q_SIGNALS: + void readyRead(); + void invalidPacket(); + void packetWritten(); + +private: + QPacketProtocolPrivate *d; +}; + + +class Q_QML_PRIVATE_EXPORT QPacket : public QDataStream +{ +public: + QPacket(); + QPacket(const QPacket &); + virtual ~QPacket(); + + void clear(); + bool isEmpty() const; + QByteArray data() const; + +protected: + friend class QPacketProtocol; + QPacket(const QByteArray &ba); + QByteArray b; + QBuffer *buf; +}; + +class Q_QML_PRIVATE_EXPORT QPacketAutoSend : public QPacket +{ +public: + virtual ~QPacketAutoSend(); + +private: + friend class QPacketProtocol; + QPacketAutoSend(QPacketProtocol *); + QPacketProtocol *p; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/qml/debugger/qqmldebug.h b/src/qml/debugger/qqmldebug.h new file mode 100644 index 0000000000..8036032150 --- /dev/null +++ b/src/qml/debugger/qqmldebug.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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 QQMLDEBUG_H +#define QQMLDEBUG_H + +#include <QtQml/qtqmlglobal.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + + +struct Q_QML_EXPORT QQmlDebuggingEnabler +{ + QQmlDebuggingEnabler(); +}; + +// Execute code in constructor before first QQmlEngine is instantiated +#if defined(QT_DECLARATIVE_DEBUG) +static QQmlDebuggingEnabler qmlEnableDebuggingHelper; +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QQMLDEBUG_H diff --git a/src/qml/debugger/qqmldebugclient.cpp b/src/qml/debugger/qqmldebugclient.cpp new file mode 100644 index 0000000000..12276b48fa --- /dev/null +++ b/src/qml/debugger/qqmldebugclient.cpp @@ -0,0 +1,421 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qqmldebugclient_p.h" + +#include "qpacketprotocol_p.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qstringlist.h> +#include <QtNetwork/qnetworkproxy.h> + +#include <private/qobject_p.h> + +QT_BEGIN_NAMESPACE + +const int protocolVersion = 1; +const QString serverId = QLatin1String("QQmlDebugServer"); +const QString clientId = QLatin1String("QQmlDebugClient"); + +class QQmlDebugClientPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQmlDebugClient) +public: + QQmlDebugClientPrivate(); + + QString name; + QQmlDebugConnection *connection; +}; + +class QQmlDebugConnectionPrivate : public QObject +{ + Q_OBJECT +public: + QQmlDebugConnectionPrivate(QQmlDebugConnection *c); + QQmlDebugConnection *q; + QPacketProtocol *protocol; + QIODevice *device; + + bool gotHello; + QHash <QString, float> serverPlugins; + QHash<QString, QQmlDebugClient *> plugins; + + void advertisePlugins(); + void connectDeviceSignals(); + +public Q_SLOTS: + void connected(); + void readyRead(); + void deviceAboutToClose(); +}; + +QQmlDebugConnectionPrivate::QQmlDebugConnectionPrivate(QQmlDebugConnection *c) + : QObject(c), q(c), protocol(0), device(0), gotHello(false) +{ + protocol = new QPacketProtocol(q, this); + QObject::connect(c, SIGNAL(connected()), this, SLOT(connected())); + QObject::connect(protocol, SIGNAL(readyRead()), this, SLOT(readyRead())); +} + +void QQmlDebugConnectionPrivate::advertisePlugins() +{ + if (!q->isConnected()) + return; + + QPacket pack; + pack << serverId << 1 << plugins.keys(); + protocol->send(pack); + q->flush(); +} + +void QQmlDebugConnectionPrivate::connected() +{ + QPacket pack; + pack << serverId << 0 << protocolVersion << plugins.keys(); + protocol->send(pack); + q->flush(); +} + +void QQmlDebugConnectionPrivate::readyRead() +{ + if (!gotHello) { + QPacket pack = protocol->read(); + QString name; + + pack >> name; + + bool validHello = false; + if (name == clientId) { + int op = -1; + pack >> op; + if (op == 0) { + int version = -1; + pack >> version; + if (version == protocolVersion) { + QStringList pluginNames; + QList<float> pluginVersions; + pack >> pluginNames; + if (!pack.isEmpty()) + pack >> pluginVersions; + + const int pluginNamesSize = pluginNames.size(); + const int pluginVersionsSize = pluginVersions.size(); + for (int i = 0; i < pluginNamesSize; ++i) { + float pluginVersion = 1.0; + if (i < pluginVersionsSize) + pluginVersion = pluginVersions.at(i); + serverPlugins.insert(pluginNames.at(i), pluginVersion); + } + + validHello = true; + } + } + } + + if (!validHello) { + qWarning("QQmlDebugConnection: Invalid hello message"); + QObject::disconnect(protocol, SIGNAL(readyRead()), this, SLOT(readyRead())); + return; + } + gotHello = true; + + QHash<QString, QQmlDebugClient *>::Iterator iter = plugins.begin(); + for (; iter != plugins.end(); ++iter) { + QQmlDebugClient::State newState = QQmlDebugClient::Unavailable; + if (serverPlugins.contains(iter.key())) + newState = QQmlDebugClient::Enabled; + iter.value()->stateChanged(newState); + } + } + + while (protocol->packetsAvailable()) { + QPacket pack = protocol->read(); + QString name; + pack >> name; + + if (name == clientId) { + int op = -1; + pack >> op; + + if (op == 1) { + // Service Discovery + QHash<QString, float> oldServerPlugins = serverPlugins; + serverPlugins.clear(); + + QStringList pluginNames; + QList<float> pluginVersions; + pack >> pluginNames; + if (!pack.isEmpty()) + pack >> pluginVersions; + + const int pluginNamesSize = pluginNames.size(); + const int pluginVersionsSize = pluginVersions.size(); + for (int i = 0; i < pluginNamesSize; ++i) { + float pluginVersion = 1.0; + if (i < pluginVersionsSize) + pluginVersion = pluginVersions.at(i); + serverPlugins.insert(pluginNames.at(i), pluginVersion); + } + + QHash<QString, QQmlDebugClient *>::Iterator iter = plugins.begin(); + for (; iter != plugins.end(); ++iter) { + const QString pluginName = iter.key(); + QQmlDebugClient::State newSate = QQmlDebugClient::Unavailable; + if (serverPlugins.contains(pluginName)) + newSate = QQmlDebugClient::Enabled; + + if (oldServerPlugins.contains(pluginName) + != serverPlugins.contains(pluginName)) { + iter.value()->stateChanged(newSate); + } + } + } else { + qWarning() << "QQmlDebugConnection: Unknown control message id" << op; + } + } else { + QByteArray message; + pack >> message; + + QHash<QString, QQmlDebugClient *>::Iterator iter = + plugins.find(name); + if (iter == plugins.end()) { + qWarning() << "QQmlDebugConnection: Message received for missing plugin" << name; + } else { + (*iter)->messageReceived(message); + } + } + } +} + +void QQmlDebugConnectionPrivate::deviceAboutToClose() +{ + // This is nasty syntax but we want to emit our own aboutToClose signal (by calling QIODevice::close()) + // without calling the underlying device close fn as that would cause an infinite loop + q->QIODevice::close(); +} + +QQmlDebugConnection::QQmlDebugConnection(QObject *parent) + : QIODevice(parent), d(new QQmlDebugConnectionPrivate(this)) +{ +} + +QQmlDebugConnection::~QQmlDebugConnection() +{ + QHash<QString, QQmlDebugClient*>::iterator iter = d->plugins.begin(); + for (; iter != d->plugins.end(); ++iter) { + iter.value()->d_func()->connection = 0; + iter.value()->stateChanged(QQmlDebugClient::NotConnected); + } +} + +bool QQmlDebugConnection::isConnected() const +{ + return state() == QAbstractSocket::ConnectedState; +} + +qint64 QQmlDebugConnection::readData(char *data, qint64 maxSize) +{ + return d->device->read(data, maxSize); +} + +qint64 QQmlDebugConnection::writeData(const char *data, qint64 maxSize) +{ + return d->device->write(data, maxSize); +} + +qint64 QQmlDebugConnection::bytesAvailable() const +{ + return d->device->bytesAvailable(); +} + +bool QQmlDebugConnection::isSequential() const +{ + return true; +} + +void QQmlDebugConnection::close() +{ + if (isOpen()) { + QIODevice::close(); + d->device->close(); + emit stateChanged(QAbstractSocket::UnconnectedState); + + QHash<QString, QQmlDebugClient*>::iterator iter = d->plugins.begin(); + for (; iter != d->plugins.end(); ++iter) { + iter.value()->stateChanged(QQmlDebugClient::NotConnected); + } + } +} + +bool QQmlDebugConnection::waitForConnected(int msecs) +{ + QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(d->device); + if (socket) + return socket->waitForConnected(msecs); + return false; +} + +QAbstractSocket::SocketState QQmlDebugConnection::state() const +{ + QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(d->device); + if (socket) + return socket->state(); + + return QAbstractSocket::UnconnectedState; +} + +void QQmlDebugConnection::flush() +{ + QAbstractSocket *socket = qobject_cast<QAbstractSocket*>(d->device); + if (socket) { + socket->flush(); + return; + } +} + +void QQmlDebugConnection::connectToHost(const QString &hostName, quint16 port) +{ + QTcpSocket *socket = new QTcpSocket(d); + socket->setProxy(QNetworkProxy::NoProxy); + d->device = socket; + d->connectDeviceSignals(); + d->gotHello = false; + connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SIGNAL(stateChanged(QAbstractSocket::SocketState))); + connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SIGNAL(error(QAbstractSocket::SocketError))); + connect(socket, SIGNAL(connected()), this, SIGNAL(connected())); + socket->connectToHost(hostName, port); + QIODevice::open(ReadWrite | Unbuffered); +} + +void QQmlDebugConnectionPrivate::connectDeviceSignals() +{ + connect(device, SIGNAL(bytesWritten(qint64)), q, SIGNAL(bytesWritten(qint64))); + connect(device, SIGNAL(readyRead()), q, SIGNAL(readyRead())); + connect(device, SIGNAL(aboutToClose()), this, SLOT(deviceAboutToClose())); +} + +// + +QQmlDebugClientPrivate::QQmlDebugClientPrivate() + : connection(0) +{ +} + +QQmlDebugClient::QQmlDebugClient(const QString &name, + QQmlDebugConnection *parent) + : QObject(*(new QQmlDebugClientPrivate), parent) +{ + Q_D(QQmlDebugClient); + d->name = name; + d->connection = parent; + + if (!d->connection) + return; + + if (d->connection->d->plugins.contains(name)) { + qWarning() << "QQmlDebugClient: Conflicting plugin name" << name; + d->connection = 0; + } else { + d->connection->d->plugins.insert(name, this); + d->connection->d->advertisePlugins(); + } +} + +QQmlDebugClient::~QQmlDebugClient() +{ + Q_D(QQmlDebugClient); + if (d->connection && d->connection->d) { + d->connection->d->plugins.remove(d->name); + d->connection->d->advertisePlugins(); + } +} + +QString QQmlDebugClient::name() const +{ + Q_D(const QQmlDebugClient); + return d->name; +} + +float QQmlDebugClient::serviceVersion() const +{ + Q_D(const QQmlDebugClient); + if (d->connection->d->serverPlugins.contains(d->name)) + return d->connection->d->serverPlugins.value(d->name); + return -1; +} + +QQmlDebugClient::State QQmlDebugClient::state() const +{ + Q_D(const QQmlDebugClient); + if (!d->connection + || !d->connection->isConnected() + || !d->connection->d->gotHello) + return NotConnected; + + if (d->connection->d->serverPlugins.contains(d->name)) + return Enabled; + + return Unavailable; +} + +void QQmlDebugClient::sendMessage(const QByteArray &message) +{ + Q_D(QQmlDebugClient); + if (state() != Enabled) + return; + + QPacket pack; + pack << d->name << message; + d->connection->d->protocol->send(pack); + d->connection->flush(); +} + +void QQmlDebugClient::stateChanged(State) +{ +} + +void QQmlDebugClient::messageReceived(const QByteArray &) +{ +} + +QT_END_NAMESPACE + +#include <qqmldebugclient.moc> diff --git a/src/qml/debugger/qqmldebugclient_p.h b/src/qml/debugger/qqmldebugclient_p.h new file mode 100644 index 0000000000..064e15cf49 --- /dev/null +++ b/src/qml/debugger/qqmldebugclient_p.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** 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 QQMLDEBUGCLIENT_H +#define QQMLDEBUGCLIENT_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 <QtNetwork/qtcpsocket.h> + +#include <private/qtqmlglobal_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + + +class QQmlDebugConnectionPrivate; +class Q_QML_PRIVATE_EXPORT QQmlDebugConnection : public QIODevice +{ + Q_OBJECT + Q_DISABLE_COPY(QQmlDebugConnection) +public: + QQmlDebugConnection(QObject * = 0); + ~QQmlDebugConnection(); + + void connectToHost(const QString &hostName, quint16 port); + + qint64 bytesAvailable() const; + bool isConnected() const; + QAbstractSocket::SocketState state() const; + void flush(); + bool isSequential() const; + void close(); + bool waitForConnected(int msecs = 30000); + +signals: + void connected(); + void stateChanged(QAbstractSocket::SocketState socketState); + void error(QAbstractSocket::SocketError socketError); + +protected: + qint64 readData(char *data, qint64 maxSize); + qint64 writeData(const char *data, qint64 maxSize); + +private: + QQmlDebugConnectionPrivate *d; + friend class QQmlDebugClient; + friend class QQmlDebugClientPrivate; +}; + +class QQmlDebugClientPrivate; +class Q_QML_PRIVATE_EXPORT QQmlDebugClient : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQmlDebugClient) + Q_DISABLE_COPY(QQmlDebugClient) + +public: + enum State { NotConnected, Unavailable, Enabled }; + + QQmlDebugClient(const QString &, QQmlDebugConnection *parent); + ~QQmlDebugClient(); + + QString name() const; + float serviceVersion() const; + State state() const; + + virtual void sendMessage(const QByteArray &); + +protected: + virtual void stateChanged(State); + virtual void messageReceived(const QByteArray &); + +private: + friend class QQmlDebugConnection; + friend class QQmlDebugConnectionPrivate; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QQMLDEBUGCLIENT_H diff --git a/src/qml/debugger/qqmldebughelper.cpp b/src/qml/debugger/qqmldebughelper.cpp new file mode 100644 index 0000000000..7158b3609d --- /dev/null +++ b/src/qml/debugger/qqmldebughelper.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qqmldebughelper_p.h" + +#include <QtCore/QAbstractAnimation> +#include <QtQml/QJSEngine> + +#include <private/qqmlengine_p.h> +#include <private/qabstractanimation_p.h> +#include <private/qqmlengine_p.h> + +QT_BEGIN_NAMESPACE + +void QQmlDebugHelper::setAnimationSlowDownFactor(qreal factor) +{ + QUnifiedTimer *timer = QUnifiedTimer::instance(); + timer->setSlowModeEnabled(factor != 1.0); + timer->setSlowdownFactor(factor); +} + +void QQmlDebugHelper::enableDebugging() { + qWarning("QQmlDebugHelper::enableDebugging() is deprecated! Add CONFIG += declarative_debug to your .pro file instead."); +#ifndef QQML_NO_DEBUG_PROTOCOL + if (!QQmlEnginePrivate::qml_debugging_enabled) { + qWarning("Qml debugging is enabled. Only use this in a safe environment!"); + } + QQmlEnginePrivate::qml_debugging_enabled = true; +#endif +} + +QT_END_NAMESPACE diff --git a/src/qml/debugger/qqmldebughelper_p.h b/src/qml/debugger/qqmldebughelper_p.h new file mode 100644 index 0000000000..5d2bcc2be0 --- /dev/null +++ b/src/qml/debugger/qqmldebughelper_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** 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 QQMLDEBUGHELPER_P_H +#define QQMLDEBUGHELPER_P_H + +#include <private/qtqmlglobal_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. +// + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QQmlEngine; + +#ifndef QT_BUILD_QML_LIB +#warning Use of this header file is deprecated! Add CONFIG += declarative_debug to your .pro file instead. +#endif + +// Helper methods to access private API through a stable interface +// This is used in the qmljsdebugger library of QtCreator. +class Q_QML_EXPORT QQmlDebugHelper +{ +public: + static void setAnimationSlowDownFactor(qreal factor); + + // Enables remote debugging functionality + // Only use this for debugging in a safe environment! + static void enableDebugging(); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QQMLDEBUGHELPER_P_H diff --git a/src/qml/debugger/qqmldebugserver.cpp b/src/qml/debugger/qqmldebugserver.cpp new file mode 100644 index 0000000000..8d5c597a78 --- /dev/null +++ b/src/qml/debugger/qqmldebugserver.cpp @@ -0,0 +1,540 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qqmldebugserver_p.h" +#include "qqmldebugservice_p.h" +#include "qqmldebugservice_p_p.h" +#include <private/qqmlengine_p.h> +#include <private/qqmlglobal_p.h> + +#include <QtCore/QDir> +#include <QtCore/QPluginLoader> +#include <QtCore/QStringList> +#include <QtCore/qwaitcondition.h> + +#include <private/qobject_p.h> +#include <private/qcoreapplication_p.h> + +QT_BEGIN_NAMESPACE + +/* + QQmlDebug Protocol (Version 1): + + handshake: + 1. Client sends + "QQmlDebugServer" 0 version pluginNames + version: an int representing the highest protocol version the client knows + pluginNames: plugins available on client side + 2. Server sends + "QQmlDebugClient" 0 version pluginNames pluginVersions + version: an int representing the highest protocol version the client & server know + pluginNames: plugins available on server side. plugins both in the client and server message are enabled. + client plugin advertisement + 1. Client sends + "QQmlDebugServer" 1 pluginNames + server plugin advertisement + 1. Server sends + "QQmlDebugClient" 1 pluginNames pluginVersions + plugin communication: + Everything send with a header different to "QQmlDebugServer" is sent to the appropriate plugin. + */ + +const int protocolVersion = 1; + +// print detailed information about loading of plugins +DEFINE_BOOL_CONFIG_OPTION(qmlDebugVerbose, QML_DEBUGGER_VERBOSE) + +class QQmlDebugServerThread; + +class QQmlDebugServerPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQmlDebugServer) +public: + QQmlDebugServerPrivate(); + + void advertisePlugins(); + QQmlDebugServerConnection *loadConnectionPlugin(const QString &pluginName); + + QQmlDebugServerConnection *connection; + QHash<QString, QQmlDebugService *> plugins; + mutable QReadWriteLock pluginsLock; + QStringList clientPlugins; + bool gotHello; + + QMutex messageArrivedMutex; + QWaitCondition messageArrivedCondition; + QStringList waitingForMessageNames; + QQmlDebugServerThread *thread; + QPluginLoader loader; + +private: + // private slot + void _q_sendMessages(const QList<QByteArray> &messages); +}; + +class QQmlDebugServerThread : public QThread +{ +public: + void setPluginName(const QString &pluginName) { + m_pluginName = pluginName; + } + + void setPort(int port, bool block) { + m_port = port; + m_block = block; + } + + void run(); + +private: + QString m_pluginName; + int m_port; + bool m_block; +}; + +QQmlDebugServerPrivate::QQmlDebugServerPrivate() : + connection(0), + gotHello(false), + thread(0) +{ + // used in _q_sendMessages + qRegisterMetaType<QList<QByteArray> >("QList<QByteArray>"); +} + +void QQmlDebugServerPrivate::advertisePlugins() +{ + Q_Q(QQmlDebugServer); + + if (!gotHello) + return; + + QByteArray message; + { + QDataStream out(&message, QIODevice::WriteOnly); + QStringList pluginNames; + QList<float> pluginVersions; + foreach (QQmlDebugService *service, plugins.values()) { + pluginNames << service->name(); + pluginVersions << service->version(); + } + out << QString(QLatin1String("QQmlDebugClient")) << 1 << pluginNames << pluginVersions; + } + + QMetaObject::invokeMethod(q, "_q_sendMessages", Qt::QueuedConnection, Q_ARG(QList<QByteArray>, QList<QByteArray>() << message)); +} + +QQmlDebugServerConnection *QQmlDebugServerPrivate::loadConnectionPlugin( + const QString &pluginName) +{ +#ifndef QT_NO_LIBRARY + QStringList pluginCandidates; + const QStringList paths = QCoreApplication::libraryPaths(); + foreach (const QString &libPath, paths) { + const QDir dir(libPath + QLatin1String("/qmltooling")); + if (dir.exists()) { + QStringList plugins(dir.entryList(QDir::Files)); + foreach (const QString &pluginPath, plugins) { + if (QFileInfo(pluginPath).fileName().contains(pluginName)) + pluginCandidates << dir.absoluteFilePath(pluginPath); + } + } + } + + foreach (const QString &pluginPath, pluginCandidates) { + if (qmlDebugVerbose()) + qDebug() << "QQmlDebugServer: Trying to load plugin " << pluginPath << "..."; + + loader.setFileName(pluginPath); + if (!loader.load()) { + if (qmlDebugVerbose()) + qDebug() << "QQmlDebugServer: Error while loading: " << loader.errorString(); + continue; + } + if (QObject *instance = loader.instance()) + connection = qobject_cast<QQmlDebugServerConnection*>(instance); + + if (connection) { + if (qmlDebugVerbose()) + qDebug() << "QQmlDebugServer: Plugin successfully loaded."; + + return connection; + } + + if (qmlDebugVerbose()) + qDebug() << "QQmlDebugServer: Plugin does not implement interface QQmlDebugServerConnection."; + + loader.unload(); + } +#endif + return 0; +} + +void QQmlDebugServerThread::run() +{ + QQmlDebugServer *server = QQmlDebugServer::instance(); + QQmlDebugServerConnection *connection + = server->d_func()->loadConnectionPlugin(m_pluginName); + if (connection) { + connection->setServer(QQmlDebugServer::instance()); + connection->setPort(m_port, m_block); + } else { + QCoreApplicationPrivate *appD = static_cast<QCoreApplicationPrivate*>(QObjectPrivate::get(qApp)); + qWarning() << QString::fromAscii("QQmlDebugServer: Ignoring \"-qmljsdebugger=%1\". " + "Remote debugger plugin has not been found.").arg(appD->qmljsDebugArgumentsString()); + } + + exec(); + + // make sure events still waiting are processed + QEventLoop eventLoop; + eventLoop.processEvents(QEventLoop::AllEvents); +} + +bool QQmlDebugServer::hasDebuggingClient() const +{ + Q_D(const QQmlDebugServer); + return d->connection + && d->connection->isConnected() + && d->gotHello; +} + +static QQmlDebugServer *qQmlDebugServer = 0; + + +static void cleanup() +{ + delete qQmlDebugServer; + qQmlDebugServer = 0; +} + +QQmlDebugServer *QQmlDebugServer::instance() +{ + static bool commandLineTested = false; + + if (!commandLineTested) { + commandLineTested = true; + + QCoreApplicationPrivate *appD = static_cast<QCoreApplicationPrivate*>(QObjectPrivate::get(qApp)); +#ifndef QQML_NO_DEBUG_PROTOCOL + // ### remove port definition when protocol is changed + int port = 0; + bool block = false; + bool ok = false; + + // format: qmljsdebugger=port:3768[,block] OR qmljsdebugger=ost[,block] + if (!appD->qmljsDebugArgumentsString().isEmpty()) { + if (!QQmlEnginePrivate::qml_debugging_enabled) { + qWarning() << QString::fromLatin1( + "QQmlDebugServer: Ignoring \"-qmljsdebugger=%1\". " + "Debugging has not been enabled.").arg( + appD->qmljsDebugArgumentsString()); + return 0; + } + + QString pluginName; + if (appD->qmljsDebugArgumentsString().indexOf(QLatin1String("port:")) == 0) { + int separatorIndex = appD->qmljsDebugArgumentsString().indexOf(QLatin1Char(',')); + port = appD->qmljsDebugArgumentsString().mid(5, separatorIndex - 5).toInt(&ok); + pluginName = QLatin1String("qmldbg_tcp"); + } else if (appD->qmljsDebugArgumentsString().contains(QLatin1String("ost"))) { + pluginName = QLatin1String("qmldbg_ost"); + ok = true; + } + + block = appD->qmljsDebugArgumentsString().contains(QLatin1String("block")); + + if (ok) { + qQmlDebugServer = new QQmlDebugServer(); + QQmlDebugServerThread *thread = new QQmlDebugServerThread; + qQmlDebugServer->d_func()->thread = thread; + qQmlDebugServer->moveToThread(thread); + thread->setPluginName(pluginName); + thread->setPort(port, block); + thread->start(); + + if (block) { + QQmlDebugServerPrivate *d = qQmlDebugServer->d_func(); + d->messageArrivedMutex.lock(); + d->messageArrivedCondition.wait(&d->messageArrivedMutex); + d->messageArrivedMutex.unlock(); + } + + } else { + qWarning() << QString::fromLatin1( + "QQmlDebugServer: Ignoring \"-qmljsdebugger=%1\". " + "Format is -qmljsdebugger=port:<port>[,block]").arg( + appD->qmljsDebugArgumentsString()); + } + } +#else + if (!appD->qmljsDebugArgumentsString().isEmpty()) { + qWarning() << QString::fromLatin1( + "QQmlDebugServer: Ignoring \"-qmljsdebugger=%1\". " + "QtQml is not configured for debugging.").arg( + appD->qmljsDebugArgumentsString()); + } +#endif + } + + return qQmlDebugServer; +} + +QQmlDebugServer::QQmlDebugServer() + : QObject(*(new QQmlDebugServerPrivate)) +{ + qAddPostRoutine(cleanup); +} + +QQmlDebugServer::~QQmlDebugServer() +{ + Q_D(QQmlDebugServer); + + QReadLocker(&d->pluginsLock); + { + foreach (QQmlDebugService *service, d->plugins.values()) { + service->stateAboutToBeChanged(QQmlDebugService::NotConnected); + service->d_func()->server = 0; + service->d_func()->state = QQmlDebugService::NotConnected; + service->stateChanged(QQmlDebugService::NotConnected); + } + } + + if (d->thread) { + d->thread->exit(); + d->thread->wait(); + delete d->thread; + } + delete d->connection; +} + +void QQmlDebugServer::receiveMessage(const QByteArray &message) +{ + Q_D(QQmlDebugServer); + + QDataStream in(message); + + QString name; + + in >> name; + if (name == QLatin1String("QQmlDebugServer")) { + int op = -1; + in >> op; + if (op == 0) { + int version; + in >> version >> d->clientPlugins; + + // Send the hello answer immediately, since it needs to arrive before + // the plugins below start sending messages. + QByteArray helloAnswer; + { + QDataStream out(&helloAnswer, QIODevice::WriteOnly); + QStringList pluginNames; + QList<float> pluginVersions; + foreach (QQmlDebugService *service, d->plugins.values()) { + pluginNames << service->name(); + pluginVersions << service->version(); + } + + out << QString(QLatin1String("QQmlDebugClient")) << 0 << protocolVersion << pluginNames << pluginVersions; + } + d->connection->send(QList<QByteArray>() << helloAnswer); + + d->gotHello = true; + + QReadLocker(&d->pluginsLock); + QHash<QString, QQmlDebugService*>::ConstIterator iter = d->plugins.constBegin(); + for (; iter != d->plugins.constEnd(); ++iter) { + QQmlDebugService::State newState = QQmlDebugService::Unavailable; + if (d->clientPlugins.contains(iter.key())) + newState = QQmlDebugService::Enabled; + iter.value()->d_func()->state = newState; + iter.value()->stateChanged(newState); + } + + qWarning("QQmlDebugServer: Connection established"); + d->messageArrivedCondition.wakeAll(); + + } else if (op == 1) { + + // Service Discovery + QStringList oldClientPlugins = d->clientPlugins; + in >> d->clientPlugins; + + QReadLocker(&d->pluginsLock); + QHash<QString, QQmlDebugService*>::ConstIterator iter = d->plugins.constBegin(); + for (; iter != d->plugins.constEnd(); ++iter) { + const QString pluginName = iter.key(); + QQmlDebugService::State newState = QQmlDebugService::Unavailable; + if (d->clientPlugins.contains(pluginName)) + newState = QQmlDebugService::Enabled; + + if (oldClientPlugins.contains(pluginName) + != d->clientPlugins.contains(pluginName)) { + iter.value()->d_func()->state = newState; + iter.value()->stateChanged(newState); + } + } + + } else { + qWarning("QQmlDebugServer: Invalid control message %d", op); + d->connection->disconnect(); + return; + } + + } else { + if (d->gotHello) { + QByteArray message; + in >> message; + + QReadLocker(&d->pluginsLock); + QHash<QString, QQmlDebugService *>::Iterator iter = d->plugins.find(name); + if (iter == d->plugins.end()) { + qWarning() << "QQmlDebugServer: Message received for missing plugin" << name; + } else { + (*iter)->messageReceived(message); + + if (d->waitingForMessageNames.removeOne(name)) + d->messageArrivedCondition.wakeAll(); + } + } else { + qWarning("QQmlDebugServer: Invalid hello message"); + } + + } +} + +void QQmlDebugServerPrivate::_q_sendMessages(const QList<QByteArray> &messages) +{ + if (connection) + connection->send(messages); +} + +QList<QQmlDebugService*> QQmlDebugServer::services() const +{ + const Q_D(QQmlDebugServer); + QReadLocker(&d->pluginsLock); + return d->plugins.values(); +} + +QStringList QQmlDebugServer::serviceNames() const +{ + const Q_D(QQmlDebugServer); + QReadLocker(&d->pluginsLock); + return d->plugins.keys(); +} + +bool QQmlDebugServer::addService(QQmlDebugService *service) +{ + Q_D(QQmlDebugServer); + { + QWriteLocker(&d->pluginsLock); + if (!service || d->plugins.contains(service->name())) + return false; + d->plugins.insert(service->name(), service); + } + { + QReadLocker(&d->pluginsLock); + d->advertisePlugins(); + QQmlDebugService::State newState = QQmlDebugService::Unavailable; + if (d->clientPlugins.contains(service->name())) + newState = QQmlDebugService::Enabled; + service->d_func()->state = newState; + } + return true; +} + +bool QQmlDebugServer::removeService(QQmlDebugService *service) +{ + Q_D(QQmlDebugServer); + { + QWriteLocker(&d->pluginsLock); + if (!service || !d->plugins.contains(service->name())) + return false; + d->plugins.remove(service->name()); + } + { + QReadLocker(&d->pluginsLock); + QQmlDebugService::State newState = QQmlDebugService::NotConnected; + service->stateAboutToBeChanged(newState); + d->advertisePlugins(); + service->d_func()->server = 0; + service->d_func()->state = newState; + service->stateChanged(newState); + } + + return true; +} + +void QQmlDebugServer::sendMessages(QQmlDebugService *service, + const QList<QByteArray> &messages) +{ + QList<QByteArray> prefixedMessages; + foreach (const QByteArray &message, messages) { + QByteArray prefixed; + QDataStream out(&prefixed, QIODevice::WriteOnly); + out << service->name() << message; + prefixedMessages << prefixed; + } + + QMetaObject::invokeMethod(this, "_q_sendMessages", Qt::QueuedConnection, Q_ARG(QList<QByteArray>, prefixedMessages)); +} + +bool QQmlDebugServer::waitForMessage(QQmlDebugService *service) +{ + Q_D(QQmlDebugServer); + QReadLocker(&d->pluginsLock); + + if (!service + || !d->plugins.contains(service->name())) + return false; + + d->messageArrivedMutex.lock(); + d->waitingForMessageNames << service->name(); + do { + d->messageArrivedCondition.wait(&d->messageArrivedMutex); + } while (d->waitingForMessageNames.contains(service->name())); + d->messageArrivedMutex.unlock(); + return true; +} + +QT_END_NAMESPACE + +#include "moc_qqmldebugserver_p.cpp" diff --git a/src/qml/debugger/qqmldebugserver_p.h b/src/qml/debugger/qqmldebugserver_p.h new file mode 100644 index 0000000000..9c6b5435c8 --- /dev/null +++ b/src/qml/debugger/qqmldebugserver_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** 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 QQMLDEBUGSERVER_H +#define QQMLDEBUGSERVER_H + +#include <QtQml/qtqmlglobal.h> +#include <private/qqmldebugserverconnection_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. +// + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + + +class QQmlDebugService; + +class QQmlDebugServerPrivate; +class Q_QML_EXPORT QQmlDebugServer : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQmlDebugServer) + Q_DISABLE_COPY(QQmlDebugServer) +public: + ~QQmlDebugServer(); + + static QQmlDebugServer *instance(); + + void setConnection(QQmlDebugServerConnection *connection); + + bool hasDebuggingClient() const; + + QList<QQmlDebugService*> services() const; + QStringList serviceNames() const; + + + bool addService(QQmlDebugService *service); + bool removeService(QQmlDebugService *service); + + void receiveMessage(const QByteArray &message); + + bool waitForMessage(QQmlDebugService *service); + void sendMessages(QQmlDebugService *service, const QList<QByteArray> &messages); + +private: + friend class QQmlDebugService; + friend class QQmlDebugServicePrivate; + friend class QQmlDebugServerThread; + QQmlDebugServer(); + Q_PRIVATE_SLOT(d_func(), void _q_sendMessages(QList<QByteArray>)) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QQMLDEBUGSERVICE_H diff --git a/src/qml/debugger/qqmldebugserverconnection_p.h b/src/qml/debugger/qqmldebugserverconnection_p.h new file mode 100644 index 0000000000..c9092f1911 --- /dev/null +++ b/src/qml/debugger/qqmldebugserverconnection_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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 QQMLDEBUGSERVERCONNECTION_H +#define QQMLDEBUGSERVERCONNECTION_H + +#include <QtQml/qtqmlglobal.h> +#include <QtCore/QtPlugin> + +// +// 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. +// + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + + +class QQmlDebugServer; +class Q_QML_EXPORT QQmlDebugServerConnection +{ +public: + QQmlDebugServerConnection() {} + virtual ~QQmlDebugServerConnection() {} + + virtual void setServer(QQmlDebugServer *server) = 0; + virtual void setPort(int port, bool bock) = 0; + virtual bool isConnected() const = 0; + virtual void send(const QList<QByteArray> &messages) = 0; + virtual void disconnect() = 0; + virtual bool waitForMessage() = 0; +}; + +Q_DECLARE_INTERFACE(QQmlDebugServerConnection, "com.trolltech.Qt.QQmlDebugServerConnection/1.0") + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QQMLDEBUGSERVERCONNECTION_H diff --git a/src/qml/debugger/qqmldebugservice.cpp b/src/qml/debugger/qqmldebugservice.cpp new file mode 100644 index 0000000000..9eb9489566 --- /dev/null +++ b/src/qml/debugger/qqmldebugservice.cpp @@ -0,0 +1,268 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qqmldebugservice_p.h" +#include "qqmldebugservice_p_p.h" +#include "qqmldebugserver_p.h" + +#include <QtCore/QDebug> +#include <QtCore/QStringList> + +QT_BEGIN_NAMESPACE + +QQmlDebugServicePrivate::QQmlDebugServicePrivate() + : server(0) +{ +} + +QQmlDebugService::QQmlDebugService(const QString &name, float version, QObject *parent) + : QObject(*(new QQmlDebugServicePrivate), parent) +{ + Q_D(QQmlDebugService); + d->name = name; + d->version = version; + d->server = QQmlDebugServer::instance(); + d->state = QQmlDebugService::NotConnected; + + +} + +QQmlDebugService::QQmlDebugService(QQmlDebugServicePrivate &dd, + const QString &name, float version, QObject *parent) + : QObject(dd, parent) +{ + Q_D(QQmlDebugService); + d->name = name; + d->version = version; + d->server = QQmlDebugServer::instance(); + d->state = QQmlDebugService::NotConnected; +} + +/** + Registers the service. This should be called in the constructor of the inherited class. From + then on the service might get asynchronous calls to messageReceived(). + */ +QQmlDebugService::State QQmlDebugService::registerService() +{ + Q_D(QQmlDebugService); + if (!d->server) + return NotConnected; + + if (d->server->serviceNames().contains(d->name)) { + qWarning() << "QQmlDebugService: Conflicting plugin name" << d->name; + d->server = 0; + } else { + d->server->addService(this); + } + return state(); +} + +QQmlDebugService::~QQmlDebugService() +{ + Q_D(const QQmlDebugService); + if (d->server) { + d->server->removeService(this); + } +} + +QString QQmlDebugService::name() const +{ + Q_D(const QQmlDebugService); + return d->name; +} + +float QQmlDebugService::version() const +{ + Q_D(const QQmlDebugService); + return d->version; +} + +QQmlDebugService::State QQmlDebugService::state() const +{ + Q_D(const QQmlDebugService); + return d->state; +} + +namespace { + +struct ObjectReference +{ + QPointer<QObject> object; + int id; +}; + +struct ObjectReferenceHash +{ + ObjectReferenceHash() : nextId(0) {} + + QHash<QObject *, ObjectReference> objects; + QHash<int, QObject *> ids; + + int nextId; +}; + +} +Q_GLOBAL_STATIC(ObjectReferenceHash, objectReferenceHash); + + +/*! + Returns a unique id for \a object. Calling this method multiple times + for the same object will return the same id. +*/ +int QQmlDebugService::idForObject(QObject *object) +{ + if (!object) + return -1; + + ObjectReferenceHash *hash = objectReferenceHash(); + QHash<QObject *, ObjectReference>::Iterator iter = + hash->objects.find(object); + + if (iter == hash->objects.end()) { + int id = hash->nextId++; + + hash->ids.insert(id, object); + iter = hash->objects.insert(object, ObjectReference()); + iter->object = object; + iter->id = id; + } else if (iter->object != object) { + int id = hash->nextId++; + + hash->ids.remove(iter->id); + + hash->ids.insert(id, object); + iter->object = object; + iter->id = id; + } + return iter->id; +} + +/*! + Returns the object for unique \a id. If the object has not previously been + assigned an id, through idForObject(), then 0 is returned. If the object + has been destroyed, 0 is returned. +*/ +QObject *QQmlDebugService::objectForId(int id) +{ + ObjectReferenceHash *hash = objectReferenceHash(); + + QHash<int, QObject *>::Iterator iter = hash->ids.find(id); + if (iter == hash->ids.end()) + return 0; + + + QHash<QObject *, ObjectReference>::Iterator objIter = + hash->objects.find(*iter); + Q_ASSERT(objIter != hash->objects.end()); + + if (objIter->object == 0) { + hash->ids.erase(iter); + hash->objects.erase(objIter); + return 0; + } else { + return *iter; + } +} + +bool QQmlDebugService::isDebuggingEnabled() +{ + return QQmlDebugServer::instance() != 0; +} + +bool QQmlDebugService::hasDebuggingClient() +{ + return QQmlDebugServer::instance() != 0 + && QQmlDebugServer::instance()->hasDebuggingClient(); +} + +QString QQmlDebugService::objectToString(QObject *obj) +{ + if(!obj) + return QLatin1String("NULL"); + + QString objectName = obj->objectName(); + if(objectName.isEmpty()) + objectName = QLatin1String("<unnamed>"); + + QString rv = QString::fromUtf8(obj->metaObject()->className()) + + QLatin1String(": ") + objectName; + + return rv; +} + +void QQmlDebugService::sendMessage(const QByteArray &message) +{ + sendMessages(QList<QByteArray>() << message); +} + +void QQmlDebugService::sendMessages(const QList<QByteArray> &messages) +{ + Q_D(QQmlDebugService); + + if (state() != Enabled) + return; + + d->server->sendMessages(this, messages); +} + +bool QQmlDebugService::waitForMessage() +{ + Q_D(QQmlDebugService); + + if (state() != Enabled) + return false; + + return d->server->waitForMessage(this); +} + +void QQmlDebugService::stateAboutToBeChanged(State) +{ +} + +void QQmlDebugService::stateChanged(State) +{ +} + +void QQmlDebugService::messageReceived(const QByteArray &) +{ +} + +QT_END_NAMESPACE diff --git a/src/qml/debugger/qqmldebugservice_p.h b/src/qml/debugger/qqmldebugservice_p.h new file mode 100644 index 0000000000..f19b64f42b --- /dev/null +++ b/src/qml/debugger/qqmldebugservice_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** 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 QQMLDEBUGSERVICE_H +#define QQMLDEBUGSERVICE_H + +#include <QtCore/qobject.h> + +#include <private/qtqmlglobal_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. +// + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + + +class QQmlDebugServicePrivate; +class Q_QML_PRIVATE_EXPORT QQmlDebugService : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QQmlDebugService) + Q_DISABLE_COPY(QQmlDebugService) + +public: + explicit QQmlDebugService(const QString &, float version, QObject *parent = 0); + ~QQmlDebugService(); + + QString name() const; + float version() const; + + enum State { NotConnected, Unavailable, Enabled }; + State state() const; + + void sendMessage(const QByteArray &); + void sendMessages(const QList<QByteArray> &); + bool waitForMessage(); + + static int idForObject(QObject *); + static QObject *objectForId(int); + + static QString objectToString(QObject *obj); + + static bool isDebuggingEnabled(); + static bool hasDebuggingClient(); + +protected: + QQmlDebugService(QQmlDebugServicePrivate &dd, const QString &name, float version, QObject *parent = 0); + + State registerService(); + + virtual void stateAboutToBeChanged(State); + virtual void stateChanged(State); + virtual void messageReceived(const QByteArray &); + +private: + friend class QQmlDebugServer; + friend class QQmlDebugServerPrivate; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QQMLDEBUGSERVICE_H + diff --git a/src/qml/debugger/qqmldebugservice_p_p.h b/src/qml/debugger/qqmldebugservice_p_p.h new file mode 100644 index 0000000000..c066e41fe6 --- /dev/null +++ b/src/qml/debugger/qqmldebugservice_p_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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 QQMLDEBUGSERVICE_P_H +#define QQMLDEBUGSERVICE_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/qobject_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + + +class QQmlDebugServer; + +class QQmlDebugServicePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQmlDebugService) +public: + QQmlDebugServicePrivate(); + + QString name; + float version; + QQmlDebugServer *server; + QQmlDebugService::State state; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QQMLDEBUGSERVICE_P_H diff --git a/src/qml/debugger/qqmldebugstatesdelegate_p.h b/src/qml/debugger/qqmldebugstatesdelegate_p.h new file mode 100644 index 0000000000..6e3cc978f2 --- /dev/null +++ b/src/qml/debugger/qqmldebugstatesdelegate_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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 QQMLDEBUGSTATESDELEGATE_P_H +#define QQMLDEBUGSTATESDELEGATE_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 <QtQml/qtqmlglobal.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + + +class QQmlContext; +class QQmlProperty; +class QObject; +class QString; +class QVariant; + +class QQmlDebugStatesDelegate +{ +protected: + QQmlDebugStatesDelegate() {} + +public: + virtual ~QQmlDebugStatesDelegate() {} + + virtual void buildStatesList(QQmlContext *ctxt, bool cleanList) = 0; + virtual void updateBinding(QQmlContext *context, + const QQmlProperty &property, + const QVariant &expression, bool isLiteralValue, + const QString &fileName, int line, int column, + bool *inBaseState) = 0; + virtual bool setBindingForInvalidProperty(QObject *object, + const QString &propertyName, + const QVariant &expression, + bool isLiteralValue) = 0; + virtual void resetBindingForInvalidProperty(QObject *object, + const QString &propertyName) = 0; + +private: + Q_DISABLE_COPY(QQmlDebugStatesDelegate) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QQMLDEBUGSTATESDELEGATE_P_H diff --git a/src/qml/debugger/qqmlenginedebug.cpp b/src/qml/debugger/qqmlenginedebug.cpp new file mode 100644 index 0000000000..597e7aeb04 --- /dev/null +++ b/src/qml/debugger/qqmlenginedebug.cpp @@ -0,0 +1,1072 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qqmlenginedebug_p.h" + +#include "qqmldebugclient_p.h" + +#include "qqmlenginedebugservice_p.h" + +#include <private/qobject_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlEngineDebugClient : public QQmlDebugClient +{ +public: + QQmlEngineDebugClient(QQmlDebugConnection *client, QQmlEngineDebugPrivate *p); + +protected: + virtual void stateChanged(State state); + virtual void messageReceived(const QByteArray &); + +private: + QQmlEngineDebugPrivate *priv; + friend class QQmlEngineDebugPrivate; +}; + +class QQmlEngineDebugPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQmlEngineDebug) +public: + QQmlEngineDebugPrivate(QQmlDebugConnection *); + ~QQmlEngineDebugPrivate(); + + void stateChanged(QQmlEngineDebug::State status); + void message(const QByteArray &); + + QQmlEngineDebugClient *client; + int nextId; + int getId(); + + void decode(QDataStream &, QQmlDebugContextReference &); + void decode(QDataStream &, QQmlDebugObjectReference &, bool simple); + + static void remove(QQmlEngineDebug *, QQmlDebugEnginesQuery *); + static void remove(QQmlEngineDebug *, QQmlDebugRootContextQuery *); + static void remove(QQmlEngineDebug *, QQmlDebugObjectQuery *); + static void remove(QQmlEngineDebug *, QQmlDebugExpressionQuery *); + static void remove(QQmlEngineDebug *, QQmlDebugWatch *); + + QHash<int, QQmlDebugEnginesQuery *> enginesQuery; + QHash<int, QQmlDebugRootContextQuery *> rootContextQuery; + QHash<int, QQmlDebugObjectQuery *> objectQuery; + QHash<int, QQmlDebugExpressionQuery *> expressionQuery; + + QHash<int, QQmlDebugWatch *> watched; +}; + +QQmlEngineDebugClient::QQmlEngineDebugClient(QQmlDebugConnection *client, + QQmlEngineDebugPrivate *p) + : QQmlDebugClient(QLatin1String("QQmlEngine"), client), priv(p) +{ +} + +void QQmlEngineDebugClient::stateChanged(State status) +{ + if (priv) + priv->stateChanged(static_cast<QQmlEngineDebug::State>(status)); +} + +void QQmlEngineDebugClient::messageReceived(const QByteArray &data) +{ + if (priv) + priv->message(data); +} + +QQmlEngineDebugPrivate::QQmlEngineDebugPrivate(QQmlDebugConnection *c) + : client(new QQmlEngineDebugClient(c, this)), nextId(0) +{ +} + +QQmlEngineDebugPrivate::~QQmlEngineDebugPrivate() +{ + if (client) + client->priv = 0; + delete client; + + QHash<int, QQmlDebugEnginesQuery*>::iterator enginesIter = enginesQuery.begin(); + for (; enginesIter != enginesQuery.end(); ++enginesIter) { + enginesIter.value()->m_client = 0; + if (enginesIter.value()->state() == QQmlDebugQuery::Waiting) + enginesIter.value()->setState(QQmlDebugQuery::Error); + } + + QHash<int, QQmlDebugRootContextQuery*>::iterator rootContextIter = rootContextQuery.begin(); + for (; rootContextIter != rootContextQuery.end(); ++rootContextIter) { + rootContextIter.value()->m_client = 0; + if (rootContextIter.value()->state() == QQmlDebugQuery::Waiting) + rootContextIter.value()->setState(QQmlDebugQuery::Error); + } + + QHash<int, QQmlDebugObjectQuery*>::iterator objectIter = objectQuery.begin(); + for (; objectIter != objectQuery.end(); ++objectIter) { + objectIter.value()->m_client = 0; + if (objectIter.value()->state() == QQmlDebugQuery::Waiting) + objectIter.value()->setState(QQmlDebugQuery::Error); + } + + QHash<int, QQmlDebugExpressionQuery*>::iterator exprIter = expressionQuery.begin(); + for (; exprIter != expressionQuery.end(); ++exprIter) { + exprIter.value()->m_client = 0; + if (exprIter.value()->state() == QQmlDebugQuery::Waiting) + exprIter.value()->setState(QQmlDebugQuery::Error); + } + + QHash<int, QQmlDebugWatch*>::iterator watchIter = watched.begin(); + for (; watchIter != watched.end(); ++watchIter) { + watchIter.value()->m_client = 0; + watchIter.value()->setState(QQmlDebugWatch::Dead); + } +} + +int QQmlEngineDebugPrivate::getId() +{ + return nextId++; +} + +void QQmlEngineDebugPrivate::remove(QQmlEngineDebug *c, QQmlDebugEnginesQuery *q) +{ + if (c && q) { + QQmlEngineDebugPrivate *p = (QQmlEngineDebugPrivate *)QObjectPrivate::get(c); + p->enginesQuery.remove(q->m_queryId); + } +} + +void QQmlEngineDebugPrivate::remove(QQmlEngineDebug *c, + QQmlDebugRootContextQuery *q) +{ + if (c && q) { + QQmlEngineDebugPrivate *p = (QQmlEngineDebugPrivate *)QObjectPrivate::get(c); + p->rootContextQuery.remove(q->m_queryId); + } +} + +void QQmlEngineDebugPrivate::remove(QQmlEngineDebug *c, QQmlDebugObjectQuery *q) +{ + if (c && q) { + QQmlEngineDebugPrivate *p = (QQmlEngineDebugPrivate *)QObjectPrivate::get(c); + p->objectQuery.remove(q->m_queryId); + } +} + +void QQmlEngineDebugPrivate::remove(QQmlEngineDebug *c, QQmlDebugExpressionQuery *q) +{ + if (c && q) { + QQmlEngineDebugPrivate *p = (QQmlEngineDebugPrivate *)QObjectPrivate::get(c); + p->expressionQuery.remove(q->m_queryId); + } +} + +void QQmlEngineDebugPrivate::remove(QQmlEngineDebug *c, QQmlDebugWatch *w) +{ + if (c && w) { + QQmlEngineDebugPrivate *p = (QQmlEngineDebugPrivate *)QObjectPrivate::get(c); + p->watched.remove(w->m_queryId); + } +} + +void QQmlEngineDebugPrivate::decode(QDataStream &ds, QQmlDebugObjectReference &o, + bool simple) +{ + QQmlEngineDebugService::QQmlObjectData data; + ds >> data; + o.m_debugId = data.objectId; + o.m_class = data.objectType; + o.m_idString = data.idString; + o.m_name = data.objectName; + o.m_source.m_url = data.url; + o.m_source.m_lineNumber = data.lineNumber; + o.m_source.m_columnNumber = data.columnNumber; + o.m_contextDebugId = data.contextId; + + if (simple) + return; + + int childCount; + bool recur; + ds >> childCount >> recur; + + for (int ii = 0; ii < childCount; ++ii) { + o.m_children.append(QQmlDebugObjectReference()); + decode(ds, o.m_children.last(), !recur); + } + + int propCount; + ds >> propCount; + + for (int ii = 0; ii < propCount; ++ii) { + QQmlEngineDebugService::QQmlObjectProperty data; + ds >> data; + QQmlDebugPropertyReference prop; + prop.m_objectDebugId = o.m_debugId; + prop.m_name = data.name; + prop.m_binding = data.binding; + prop.m_hasNotifySignal = data.hasNotifySignal; + prop.m_valueTypeName = data.valueTypeName; + switch (data.type) { + case QQmlEngineDebugService::QQmlObjectProperty::Basic: + case QQmlEngineDebugService::QQmlObjectProperty::List: + case QQmlEngineDebugService::QQmlObjectProperty::SignalProperty: + { + prop.m_value = data.value; + break; + } + case QQmlEngineDebugService::QQmlObjectProperty::Object: + { + QQmlDebugObjectReference obj; + obj.m_debugId = prop.m_value.toInt(); + prop.m_value = QVariant::fromValue(obj); + break; + } + case QQmlEngineDebugService::QQmlObjectProperty::Unknown: + break; + } + o.m_properties << prop; + } +} + +void QQmlEngineDebugPrivate::decode(QDataStream &ds, QQmlDebugContextReference &c) +{ + ds >> c.m_name >> c.m_debugId; + + int contextCount; + ds >> contextCount; + + for (int ii = 0; ii < contextCount; ++ii) { + c.m_contexts.append(QQmlDebugContextReference()); + decode(ds, c.m_contexts.last()); + } + + int objectCount; + ds >> objectCount; + + for (int ii = 0; ii < objectCount; ++ii) { + QQmlDebugObjectReference obj; + decode(ds, obj, true); + + obj.m_contextDebugId = c.m_debugId; + c.m_objects << obj; + } +} + +void QQmlEngineDebugPrivate::stateChanged(QQmlEngineDebug::State status) +{ + emit q_func()->stateChanged(status); +} + +void QQmlEngineDebugPrivate::message(const QByteArray &data) +{ + QDataStream ds(data); + + QByteArray type; + ds >> type; + + //qDebug() << "QQmlEngineDebugPrivate::message()" << type; + + if (type == "LIST_ENGINES_R") { + int queryId; + ds >> queryId; + + QQmlDebugEnginesQuery *query = enginesQuery.value(queryId); + if (!query) + return; + enginesQuery.remove(queryId); + + int count; + ds >> count; + + for (int ii = 0; ii < count; ++ii) { + QQmlDebugEngineReference ref; + ds >> ref.m_name; + ds >> ref.m_debugId; + query->m_engines << ref; + } + + query->m_client = 0; + query->setState(QQmlDebugQuery::Completed); + } else if (type == "LIST_OBJECTS_R") { + int queryId; + ds >> queryId; + + QQmlDebugRootContextQuery *query = rootContextQuery.value(queryId); + if (!query) + return; + rootContextQuery.remove(queryId); + + if (!ds.atEnd()) + decode(ds, query->m_context); + + query->m_client = 0; + query->setState(QQmlDebugQuery::Completed); + } else if (type == "FETCH_OBJECT_R") { + int queryId; + ds >> queryId; + + QQmlDebugObjectQuery *query = objectQuery.value(queryId); + if (!query) + return; + objectQuery.remove(queryId); + + if (!ds.atEnd()) + decode(ds, query->m_object, false); + + query->m_client = 0; + query->setState(QQmlDebugQuery::Completed); + } else if (type == "EVAL_EXPRESSION_R") { + int queryId; + QVariant result; + ds >> queryId >> result; + + QQmlDebugExpressionQuery *query = expressionQuery.value(queryId); + if (!query) + return; + expressionQuery.remove(queryId); + + query->m_result = result; + query->m_client = 0; + query->setState(QQmlDebugQuery::Completed); + } else if (type == "WATCH_PROPERTY_R") { + int queryId; + bool ok; + ds >> queryId >> ok; + + QQmlDebugWatch *watch = watched.value(queryId); + if (!watch) + return; + + watch->setState(ok ? QQmlDebugWatch::Active : QQmlDebugWatch::Inactive); + } else if (type == "WATCH_OBJECT_R") { + int queryId; + bool ok; + ds >> queryId >> ok; + + QQmlDebugWatch *watch = watched.value(queryId); + if (!watch) + return; + + watch->setState(ok ? QQmlDebugWatch::Active : QQmlDebugWatch::Inactive); + } else if (type == "WATCH_EXPR_OBJECT_R") { + int queryId; + bool ok; + ds >> queryId >> ok; + + QQmlDebugWatch *watch = watched.value(queryId); + if (!watch) + return; + + watch->setState(ok ? QQmlDebugWatch::Active : QQmlDebugWatch::Inactive); + } else if (type == "UPDATE_WATCH") { + int queryId; + int debugId; + QByteArray name; + QVariant value; + ds >> queryId >> debugId >> name >> value; + + QQmlDebugWatch *watch = watched.value(queryId, 0); + if (!watch) + return; + emit watch->valueChanged(name, value); + } else if (type == "OBJECT_CREATED") { + emit q_func()->newObjects(); + } +} + +QQmlEngineDebug::QQmlEngineDebug(QQmlDebugConnection *client, QObject *parent) + : QObject(*(new QQmlEngineDebugPrivate(client)), parent) +{ +} + +QQmlEngineDebug::~QQmlEngineDebug() +{ +} + +QQmlEngineDebug::State QQmlEngineDebug::state() const +{ + Q_D(const QQmlEngineDebug); + + return static_cast<QQmlEngineDebug::State>(d->client->state()); +} + +QQmlDebugPropertyWatch *QQmlEngineDebug::addWatch(const QQmlDebugPropertyReference &property, QObject *parent) +{ + Q_D(QQmlEngineDebug); + + QQmlDebugPropertyWatch *watch = new QQmlDebugPropertyWatch(parent); + if (d->client->state() == QQmlDebugClient::Enabled) { + int queryId = d->getId(); + watch->m_queryId = queryId; + watch->m_client = this; + watch->m_objectDebugId = property.objectDebugId(); + watch->m_name = property.name(); + d->watched.insert(queryId, watch); + + QByteArray message; + QDataStream ds(&message, QIODevice::WriteOnly); + ds << QByteArray("WATCH_PROPERTY") << queryId << property.objectDebugId() << property.name().toUtf8(); + d->client->sendMessage(message); + } else { + watch->m_state = QQmlDebugWatch::Dead; + } + + return watch; +} + +QQmlDebugWatch *QQmlEngineDebug::addWatch(const QQmlDebugContextReference &, const QString &, QObject *) +{ + qWarning("QQmlEngineDebug::addWatch(): Not implemented"); + return 0; +} + +QQmlDebugObjectExpressionWatch *QQmlEngineDebug::addWatch(const QQmlDebugObjectReference &object, const QString &expr, QObject *parent) +{ + Q_D(QQmlEngineDebug); + QQmlDebugObjectExpressionWatch *watch = new QQmlDebugObjectExpressionWatch(parent); + if (d->client->state() == QQmlDebugClient::Enabled) { + int queryId = d->getId(); + watch->m_queryId = queryId; + watch->m_client = this; + watch->m_objectDebugId = object.debugId(); + watch->m_expr = expr; + d->watched.insert(queryId, watch); + + QByteArray message; + QDataStream ds(&message, QIODevice::WriteOnly); + ds << QByteArray("WATCH_EXPR_OBJECT") << queryId << object.debugId() << expr; + d->client->sendMessage(message); + } else { + watch->m_state = QQmlDebugWatch::Dead; + } + return watch; +} + +QQmlDebugWatch *QQmlEngineDebug::addWatch(const QQmlDebugObjectReference &object, QObject *parent) +{ + Q_D(QQmlEngineDebug); + + QQmlDebugWatch *watch = new QQmlDebugWatch(parent); + if (d->client->state() == QQmlDebugClient::Enabled) { + int queryId = d->getId(); + watch->m_queryId = queryId; + watch->m_client = this; + watch->m_objectDebugId = object.debugId(); + d->watched.insert(queryId, watch); + + QByteArray message; + QDataStream ds(&message, QIODevice::WriteOnly); + ds << QByteArray("WATCH_OBJECT") << queryId << object.debugId(); + d->client->sendMessage(message); + } else { + watch->m_state = QQmlDebugWatch::Dead; + } + + return watch; +} + +QQmlDebugWatch *QQmlEngineDebug::addWatch(const QQmlDebugFileReference &, QObject *) +{ + qWarning("QQmlEngineDebug::addWatch(): Not implemented"); + return 0; +} + +void QQmlEngineDebug::removeWatch(QQmlDebugWatch *watch) +{ + Q_D(QQmlEngineDebug); + + if (!watch || !watch->m_client) + return; + + watch->m_client = 0; + watch->setState(QQmlDebugWatch::Inactive); + + d->watched.remove(watch->queryId()); + + if (d->client && d->client->state() == QQmlDebugClient::Enabled) { + QByteArray message; + QDataStream ds(&message, QIODevice::WriteOnly); + ds << QByteArray("NO_WATCH") << watch->queryId(); + d->client->sendMessage(message); + } +} + +QQmlDebugEnginesQuery *QQmlEngineDebug::queryAvailableEngines(QObject *parent) +{ + Q_D(QQmlEngineDebug); + + QQmlDebugEnginesQuery *query = new QQmlDebugEnginesQuery(parent); + if (d->client->state() == QQmlDebugClient::Enabled) { + query->m_client = this; + int queryId = d->getId(); + query->m_queryId = queryId; + d->enginesQuery.insert(queryId, query); + + QByteArray message; + QDataStream ds(&message, QIODevice::WriteOnly); + ds << QByteArray("LIST_ENGINES") << queryId; + d->client->sendMessage(message); + } else { + query->m_state = QQmlDebugQuery::Error; + } + + return query; +} + +QQmlDebugRootContextQuery *QQmlEngineDebug::queryRootContexts(const QQmlDebugEngineReference &engine, QObject *parent) +{ + Q_D(QQmlEngineDebug); + + QQmlDebugRootContextQuery *query = new QQmlDebugRootContextQuery(parent); + if (d->client->state() == QQmlDebugClient::Enabled && engine.debugId() != -1) { + query->m_client = this; + int queryId = d->getId(); + query->m_queryId = queryId; + d->rootContextQuery.insert(queryId, query); + + QByteArray message; + QDataStream ds(&message, QIODevice::WriteOnly); + ds << QByteArray("LIST_OBJECTS") << queryId << engine.debugId(); + d->client->sendMessage(message); + } else { + query->m_state = QQmlDebugQuery::Error; + } + + return query; +} + +QQmlDebugObjectQuery *QQmlEngineDebug::queryObject(const QQmlDebugObjectReference &object, QObject *parent) +{ + Q_D(QQmlEngineDebug); + + QQmlDebugObjectQuery *query = new QQmlDebugObjectQuery(parent); + if (d->client->state() == QQmlDebugClient::Enabled && object.debugId() != -1) { + query->m_client = this; + int queryId = d->getId(); + query->m_queryId = queryId; + d->objectQuery.insert(queryId, query); + + QByteArray message; + QDataStream ds(&message, QIODevice::WriteOnly); + ds << QByteArray("FETCH_OBJECT") << queryId << object.debugId() + << false << true; + d->client->sendMessage(message); + } else { + query->m_state = QQmlDebugQuery::Error; + } + + return query; +} + +QQmlDebugObjectQuery *QQmlEngineDebug::queryObjectRecursive(const QQmlDebugObjectReference &object, QObject *parent) +{ + Q_D(QQmlEngineDebug); + + QQmlDebugObjectQuery *query = new QQmlDebugObjectQuery(parent); + if (d->client->state() == QQmlDebugClient::Enabled && object.debugId() != -1) { + query->m_client = this; + int queryId = d->getId(); + query->m_queryId = queryId; + d->objectQuery.insert(queryId, query); + + QByteArray message; + QDataStream ds(&message, QIODevice::WriteOnly); + ds << QByteArray("FETCH_OBJECT") << queryId << object.debugId() + << true << true; + d->client->sendMessage(message); + } else { + query->m_state = QQmlDebugQuery::Error; + } + + return query; +} + +QQmlDebugExpressionQuery *QQmlEngineDebug::queryExpressionResult(int objectDebugId, const QString &expr, QObject *parent) +{ + Q_D(QQmlEngineDebug); + + QQmlDebugExpressionQuery *query = new QQmlDebugExpressionQuery(parent); + if (d->client->state() == QQmlDebugClient::Enabled && objectDebugId != -1) { + query->m_client = this; + query->m_expr = expr; + int queryId = d->getId(); + query->m_queryId = queryId; + d->expressionQuery.insert(queryId, query); + + QByteArray message; + QDataStream ds(&message, QIODevice::WriteOnly); + ds << QByteArray("EVAL_EXPRESSION") << queryId << objectDebugId << expr; + d->client->sendMessage(message); + } else { + query->m_state = QQmlDebugQuery::Error; + } + + return query; +} + +bool QQmlEngineDebug::setBindingForObject(int objectDebugId, const QString &propertyName, + const QVariant &bindingExpression, + bool isLiteralValue, + QString source, int line) +{ + Q_D(QQmlEngineDebug); + + if (d->client->state() == QQmlDebugClient::Enabled && objectDebugId != -1) { + QByteArray message; + QDataStream ds(&message, QIODevice::WriteOnly); + ds << QByteArray("SET_BINDING") << objectDebugId << propertyName << bindingExpression << isLiteralValue << source << line; + d->client->sendMessage(message); + return true; + } else { + return false; + } +} + +bool QQmlEngineDebug::resetBindingForObject(int objectDebugId, const QString &propertyName) +{ + Q_D(QQmlEngineDebug); + + if (d->client->state() == QQmlDebugClient::Enabled && objectDebugId != -1) { + QByteArray message; + QDataStream ds(&message, QIODevice::WriteOnly); + ds << QByteArray("RESET_BINDING") << objectDebugId << propertyName; + d->client->sendMessage(message); + return true; + } else { + return false; + } +} + +bool QQmlEngineDebug::setMethodBody(int objectDebugId, const QString &methodName, + const QString &methodBody) +{ + Q_D(QQmlEngineDebug); + + if (d->client->state() == QQmlDebugClient::Enabled && objectDebugId != -1) { + QByteArray message; + QDataStream ds(&message, QIODevice::WriteOnly); + ds << QByteArray("SET_METHOD_BODY") << objectDebugId << methodName << methodBody; + d->client->sendMessage(message); + return true; + } else { + return false; + } +} + +QQmlDebugWatch::QQmlDebugWatch(QObject *parent) + : QObject(parent), m_state(Waiting), m_queryId(-1), m_client(0), m_objectDebugId(-1) +{ +} + +QQmlDebugWatch::~QQmlDebugWatch() +{ + if (m_client && m_queryId != -1) + QQmlEngineDebugPrivate::remove(m_client, this); +} + +int QQmlDebugWatch::queryId() const +{ + return m_queryId; +} + +int QQmlDebugWatch::objectDebugId() const +{ + return m_objectDebugId; +} + +QQmlDebugWatch::State QQmlDebugWatch::state() const +{ + return m_state; +} + +void QQmlDebugWatch::setState(State s) +{ + if (m_state == s) + return; + m_state = s; + emit stateChanged(m_state); +} + +QQmlDebugPropertyWatch::QQmlDebugPropertyWatch(QObject *parent) + : QQmlDebugWatch(parent) +{ +} + +QString QQmlDebugPropertyWatch::name() const +{ + return m_name; +} + + +QQmlDebugObjectExpressionWatch::QQmlDebugObjectExpressionWatch(QObject *parent) + : QQmlDebugWatch(parent) +{ +} + +QString QQmlDebugObjectExpressionWatch::expression() const +{ + return m_expr; +} + + +QQmlDebugQuery::QQmlDebugQuery(QObject *parent) + : QObject(parent), m_state(Waiting) +{ +} + +QQmlDebugQuery::State QQmlDebugQuery::state() const +{ + return m_state; +} + +bool QQmlDebugQuery::isWaiting() const +{ + return m_state == Waiting; +} + +void QQmlDebugQuery::setState(State s) +{ + if (m_state == s) + return; + m_state = s; + emit stateChanged(m_state); +} + +QQmlDebugEnginesQuery::QQmlDebugEnginesQuery(QObject *parent) + : QQmlDebugQuery(parent), m_client(0), m_queryId(-1) +{ +} + +QQmlDebugEnginesQuery::~QQmlDebugEnginesQuery() +{ + if (m_client && m_queryId != -1) + QQmlEngineDebugPrivate::remove(m_client, this); +} + +QList<QQmlDebugEngineReference> QQmlDebugEnginesQuery::engines() const +{ + return m_engines; +} + +QQmlDebugRootContextQuery::QQmlDebugRootContextQuery(QObject *parent) + : QQmlDebugQuery(parent), m_client(0), m_queryId(-1) +{ +} + +QQmlDebugRootContextQuery::~QQmlDebugRootContextQuery() +{ + if (m_client && m_queryId != -1) + QQmlEngineDebugPrivate::remove(m_client, this); +} + +QQmlDebugContextReference QQmlDebugRootContextQuery::rootContext() const +{ + return m_context; +} + +QQmlDebugObjectQuery::QQmlDebugObjectQuery(QObject *parent) + : QQmlDebugQuery(parent), m_client(0), m_queryId(-1) +{ +} + +QQmlDebugObjectQuery::~QQmlDebugObjectQuery() +{ + if (m_client && m_queryId != -1) + QQmlEngineDebugPrivate::remove(m_client, this); +} + +QQmlDebugObjectReference QQmlDebugObjectQuery::object() const +{ + return m_object; +} + +QQmlDebugExpressionQuery::QQmlDebugExpressionQuery(QObject *parent) + : QQmlDebugQuery(parent), m_client(0), m_queryId(-1) +{ +} + +QQmlDebugExpressionQuery::~QQmlDebugExpressionQuery() +{ + if (m_client && m_queryId != -1) + QQmlEngineDebugPrivate::remove(m_client, this); +} + +QVariant QQmlDebugExpressionQuery::expression() const +{ + return m_expr; +} + +QVariant QQmlDebugExpressionQuery::result() const +{ + return m_result; +} + +QQmlDebugEngineReference::QQmlDebugEngineReference() + : m_debugId(-1) +{ +} + +QQmlDebugEngineReference::QQmlDebugEngineReference(int debugId) + : m_debugId(debugId) +{ +} + +QQmlDebugEngineReference::QQmlDebugEngineReference(const QQmlDebugEngineReference &o) + : m_debugId(o.m_debugId), m_name(o.m_name) +{ +} + +QQmlDebugEngineReference & +QQmlDebugEngineReference::operator=(const QQmlDebugEngineReference &o) +{ + m_debugId = o.m_debugId; m_name = o.m_name; + return *this; +} + +int QQmlDebugEngineReference::debugId() const +{ + return m_debugId; +} + +QString QQmlDebugEngineReference::name() const +{ + return m_name; +} + +QQmlDebugObjectReference::QQmlDebugObjectReference() + : m_debugId(-1), m_contextDebugId(-1) +{ +} + +QQmlDebugObjectReference::QQmlDebugObjectReference(int debugId) + : m_debugId(debugId), m_contextDebugId(-1) +{ +} + +QQmlDebugObjectReference::QQmlDebugObjectReference(const QQmlDebugObjectReference &o) + : m_debugId(o.m_debugId), m_class(o.m_class), m_idString(o.m_idString), + m_name(o.m_name), m_source(o.m_source), m_contextDebugId(o.m_contextDebugId), + m_properties(o.m_properties), m_children(o.m_children) +{ +} + +QQmlDebugObjectReference & +QQmlDebugObjectReference::operator=(const QQmlDebugObjectReference &o) +{ + m_debugId = o.m_debugId; m_class = o.m_class; m_idString = o.m_idString; + m_name = o.m_name; m_source = o.m_source; m_contextDebugId = o.m_contextDebugId; + m_properties = o.m_properties; m_children = o.m_children; + return *this; +} + +int QQmlDebugObjectReference::debugId() const +{ + return m_debugId; +} + +QString QQmlDebugObjectReference::className() const +{ + return m_class; +} + +QString QQmlDebugObjectReference::idString() const +{ + return m_idString; +} + +QString QQmlDebugObjectReference::name() const +{ + return m_name; +} + +QQmlDebugFileReference QQmlDebugObjectReference::source() const +{ + return m_source; +} + +int QQmlDebugObjectReference::contextDebugId() const +{ + return m_contextDebugId; +} + +QList<QQmlDebugPropertyReference> QQmlDebugObjectReference::properties() const +{ + return m_properties; +} + +QList<QQmlDebugObjectReference> QQmlDebugObjectReference::children() const +{ + return m_children; +} + +QQmlDebugContextReference::QQmlDebugContextReference() + : m_debugId(-1) +{ +} + +QQmlDebugContextReference::QQmlDebugContextReference(const QQmlDebugContextReference &o) + : m_debugId(o.m_debugId), m_name(o.m_name), m_objects(o.m_objects), m_contexts(o.m_contexts) +{ +} + +QQmlDebugContextReference &QQmlDebugContextReference::operator=(const QQmlDebugContextReference &o) +{ + m_debugId = o.m_debugId; m_name = o.m_name; m_objects = o.m_objects; + m_contexts = o.m_contexts; + return *this; +} + +int QQmlDebugContextReference::debugId() const +{ + return m_debugId; +} + +QString QQmlDebugContextReference::name() const +{ + return m_name; +} + +QList<QQmlDebugObjectReference> QQmlDebugContextReference::objects() const +{ + return m_objects; +} + +QList<QQmlDebugContextReference> QQmlDebugContextReference::contexts() const +{ + return m_contexts; +} + +QQmlDebugFileReference::QQmlDebugFileReference() + : m_lineNumber(-1), m_columnNumber(-1) +{ +} + +QQmlDebugFileReference::QQmlDebugFileReference(const QQmlDebugFileReference &o) + : m_url(o.m_url), m_lineNumber(o.m_lineNumber), m_columnNumber(o.m_columnNumber) +{ +} + +QQmlDebugFileReference &QQmlDebugFileReference::operator=(const QQmlDebugFileReference &o) +{ + m_url = o.m_url; m_lineNumber = o.m_lineNumber; m_columnNumber = o.m_columnNumber; + return *this; +} + +QUrl QQmlDebugFileReference::url() const +{ + return m_url; +} + +void QQmlDebugFileReference::setUrl(const QUrl &u) +{ + m_url = u; +} + +int QQmlDebugFileReference::lineNumber() const +{ + return m_lineNumber; +} + +void QQmlDebugFileReference::setLineNumber(int l) +{ + m_lineNumber = l; +} + +int QQmlDebugFileReference::columnNumber() const +{ + return m_columnNumber; +} + +void QQmlDebugFileReference::setColumnNumber(int c) +{ + m_columnNumber = c; +} + +QQmlDebugPropertyReference::QQmlDebugPropertyReference() + : m_objectDebugId(-1), m_hasNotifySignal(false) +{ +} + +QQmlDebugPropertyReference::QQmlDebugPropertyReference(const QQmlDebugPropertyReference &o) + : m_objectDebugId(o.m_objectDebugId), m_name(o.m_name), m_value(o.m_value), + m_valueTypeName(o.m_valueTypeName), m_binding(o.m_binding), + m_hasNotifySignal(o.m_hasNotifySignal) +{ +} + +QQmlDebugPropertyReference &QQmlDebugPropertyReference::operator=(const QQmlDebugPropertyReference &o) +{ + m_objectDebugId = o.m_objectDebugId; m_name = o.m_name; m_value = o.m_value; + m_valueTypeName = o.m_valueTypeName; m_binding = o.m_binding; + m_hasNotifySignal = o.m_hasNotifySignal; + return *this; +} + +int QQmlDebugPropertyReference::objectDebugId() const +{ + return m_objectDebugId; +} + +QString QQmlDebugPropertyReference::name() const +{ + return m_name; +} + +QString QQmlDebugPropertyReference::valueTypeName() const +{ + return m_valueTypeName; +} + +QVariant QQmlDebugPropertyReference::value() const +{ + return m_value; +} + +QString QQmlDebugPropertyReference::binding() const +{ + return m_binding; +} + +bool QQmlDebugPropertyReference::hasNotifySignal() const +{ + return m_hasNotifySignal; +} + +QT_END_NAMESPACE + diff --git a/src/qml/debugger/qqmlenginedebug_p.h b/src/qml/debugger/qqmlenginedebug_p.h new file mode 100644 index 0000000000..0562d8755b --- /dev/null +++ b/src/qml/debugger/qqmlenginedebug_p.h @@ -0,0 +1,397 @@ +/**************************************************************************** +** +** 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 QQMLENGINEDEBUG_H +#define QQMLENGINEDEBUG_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/qobject.h> +#include <QtCore/qurl.h> +#include <QtCore/qvariant.h> + +#include <private/qtqmlglobal_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + + +class QQmlDebugConnection; +class QQmlDebugWatch; +class QQmlDebugPropertyWatch; +class QQmlDebugObjectExpressionWatch; +class QQmlDebugEnginesQuery; +class QQmlDebugRootContextQuery; +class QQmlDebugObjectQuery; +class QQmlDebugExpressionQuery; +class QQmlDebugPropertyReference; +class QQmlDebugContextReference; +class QQmlDebugObjectReference; +class QQmlDebugFileReference; +class QQmlDebugEngineReference; +class QQmlEngineDebugPrivate; +class Q_QML_PRIVATE_EXPORT QQmlEngineDebug : public QObject +{ + Q_OBJECT +public: + enum State { NotConnected, Unavailable, Enabled }; + + explicit QQmlEngineDebug(QQmlDebugConnection *, QObject * = 0); + ~QQmlEngineDebug(); + + State state() const; + + QQmlDebugPropertyWatch *addWatch(const QQmlDebugPropertyReference &, + QObject *parent = 0); + QQmlDebugWatch *addWatch(const QQmlDebugContextReference &, const QString &, + QObject *parent = 0); + QQmlDebugObjectExpressionWatch *addWatch(const QQmlDebugObjectReference &, const QString &, + QObject *parent = 0); + QQmlDebugWatch *addWatch(const QQmlDebugObjectReference &, + QObject *parent = 0); + QQmlDebugWatch *addWatch(const QQmlDebugFileReference &, + QObject *parent = 0); + + void removeWatch(QQmlDebugWatch *watch); + + QQmlDebugEnginesQuery *queryAvailableEngines(QObject *parent = 0); + QQmlDebugRootContextQuery *queryRootContexts(const QQmlDebugEngineReference &, + QObject *parent = 0); + QQmlDebugObjectQuery *queryObject(const QQmlDebugObjectReference &, + QObject *parent = 0); + QQmlDebugObjectQuery *queryObjectRecursive(const QQmlDebugObjectReference &, + QObject *parent = 0); + QQmlDebugExpressionQuery *queryExpressionResult(int objectDebugId, + const QString &expr, + QObject *parent = 0); + bool setBindingForObject(int objectDebugId, const QString &propertyName, + const QVariant &bindingExpression, bool isLiteralValue, + QString source = QString(), int line = -1); + bool resetBindingForObject(int objectDebugId, const QString &propertyName); + bool setMethodBody(int objectDebugId, const QString &methodName, const QString &methodBody); + +Q_SIGNALS: + void newObjects(); + void stateChanged(State state); + +private: + Q_DECLARE_PRIVATE(QQmlEngineDebug) +}; + +class Q_QML_PRIVATE_EXPORT QQmlDebugWatch : public QObject +{ + Q_OBJECT +public: + enum State { Waiting, Active, Inactive, Dead }; + + QQmlDebugWatch(QObject *); + ~QQmlDebugWatch(); + + int queryId() const; + int objectDebugId() const; + State state() const; + +Q_SIGNALS: + void stateChanged(QQmlDebugWatch::State); + //void objectChanged(int, const QQmlDebugObjectReference &); + //void valueChanged(int, const QVariant &); + + // Server sends value as string if it is a user-type variant + void valueChanged(const QByteArray &name, const QVariant &value); + +private: + friend class QQmlEngineDebug; + friend class QQmlEngineDebugPrivate; + void setState(State); + State m_state; + int m_queryId; + QQmlEngineDebug *m_client; + int m_objectDebugId; +}; + +class Q_QML_PRIVATE_EXPORT QQmlDebugPropertyWatch : public QQmlDebugWatch +{ + Q_OBJECT +public: + QQmlDebugPropertyWatch(QObject *parent); + + QString name() const; + +private: + friend class QQmlEngineDebug; + QString m_name; +}; + +class Q_QML_PRIVATE_EXPORT QQmlDebugObjectExpressionWatch : public QQmlDebugWatch +{ + Q_OBJECT +public: + QQmlDebugObjectExpressionWatch(QObject *parent); + + QString expression() const; + +private: + friend class QQmlEngineDebug; + QString m_expr; + int m_debugId; +}; + + +class Q_QML_PRIVATE_EXPORT QQmlDebugQuery : public QObject +{ + Q_OBJECT +public: + enum State { Waiting, Error, Completed }; + + State state() const; + bool isWaiting() const; + +Q_SIGNALS: + void stateChanged(QQmlDebugQuery::State); + +protected: + QQmlDebugQuery(QObject *); + +private: + friend class QQmlEngineDebug; + friend class QQmlEngineDebugPrivate; + void setState(State); + State m_state; +}; + +class Q_QML_PRIVATE_EXPORT QQmlDebugFileReference +{ +public: + QQmlDebugFileReference(); + QQmlDebugFileReference(const QQmlDebugFileReference &); + QQmlDebugFileReference &operator=(const QQmlDebugFileReference &); + + QUrl url() const; + void setUrl(const QUrl &); + int lineNumber() const; + void setLineNumber(int); + int columnNumber() const; + void setColumnNumber(int); + +private: + friend class QQmlEngineDebugPrivate; + QUrl m_url; + int m_lineNumber; + int m_columnNumber; +}; + +class Q_QML_PRIVATE_EXPORT QQmlDebugEngineReference +{ +public: + QQmlDebugEngineReference(); + QQmlDebugEngineReference(int); + QQmlDebugEngineReference(const QQmlDebugEngineReference &); + QQmlDebugEngineReference &operator=(const QQmlDebugEngineReference &); + + int debugId() const; + QString name() const; + +private: + friend class QQmlEngineDebugPrivate; + int m_debugId; + QString m_name; +}; + +class Q_QML_PRIVATE_EXPORT QQmlDebugObjectReference +{ +public: + QQmlDebugObjectReference(); + QQmlDebugObjectReference(int); + QQmlDebugObjectReference(const QQmlDebugObjectReference &); + QQmlDebugObjectReference &operator=(const QQmlDebugObjectReference &); + + int debugId() const; + QString className() const; + QString idString() const; + QString name() const; + + QQmlDebugFileReference source() const; + int contextDebugId() const; + + QList<QQmlDebugPropertyReference> properties() const; + QList<QQmlDebugObjectReference> children() const; + +private: + friend class QQmlEngineDebugPrivate; + int m_debugId; + QString m_class; + QString m_idString; + QString m_name; + QQmlDebugFileReference m_source; + int m_contextDebugId; + QList<QQmlDebugPropertyReference> m_properties; + QList<QQmlDebugObjectReference> m_children; +}; + +class Q_QML_PRIVATE_EXPORT QQmlDebugContextReference +{ +public: + QQmlDebugContextReference(); + QQmlDebugContextReference(const QQmlDebugContextReference &); + QQmlDebugContextReference &operator=(const QQmlDebugContextReference &); + + int debugId() const; + QString name() const; + + QList<QQmlDebugObjectReference> objects() const; + QList<QQmlDebugContextReference> contexts() const; + +private: + friend class QQmlEngineDebugPrivate; + int m_debugId; + QString m_name; + QList<QQmlDebugObjectReference> m_objects; + QList<QQmlDebugContextReference> m_contexts; +}; + +class Q_QML_PRIVATE_EXPORT QQmlDebugPropertyReference +{ +public: + QQmlDebugPropertyReference(); + QQmlDebugPropertyReference(const QQmlDebugPropertyReference &); + QQmlDebugPropertyReference &operator=(const QQmlDebugPropertyReference &); + + int objectDebugId() const; + QString name() const; + QVariant value() const; + QString valueTypeName() const; + QString binding() const; + bool hasNotifySignal() const; + +private: + friend class QQmlEngineDebugPrivate; + int m_objectDebugId; + QString m_name; + QVariant m_value; + QString m_valueTypeName; + QString m_binding; + bool m_hasNotifySignal; +}; + + +class Q_QML_PRIVATE_EXPORT QQmlDebugEnginesQuery : public QQmlDebugQuery +{ + Q_OBJECT +public: + virtual ~QQmlDebugEnginesQuery(); + QList<QQmlDebugEngineReference> engines() const; +private: + friend class QQmlEngineDebug; + friend class QQmlEngineDebugPrivate; + QQmlDebugEnginesQuery(QObject *); + QQmlEngineDebug *m_client; + int m_queryId; + QList<QQmlDebugEngineReference> m_engines; +}; + +class Q_QML_PRIVATE_EXPORT QQmlDebugRootContextQuery : public QQmlDebugQuery +{ + Q_OBJECT +public: + virtual ~QQmlDebugRootContextQuery(); + QQmlDebugContextReference rootContext() const; +private: + friend class QQmlEngineDebug; + friend class QQmlEngineDebugPrivate; + QQmlDebugRootContextQuery(QObject *); + QQmlEngineDebug *m_client; + int m_queryId; + QQmlDebugContextReference m_context; +}; + +class Q_QML_PRIVATE_EXPORT QQmlDebugObjectQuery : public QQmlDebugQuery +{ + Q_OBJECT +public: + virtual ~QQmlDebugObjectQuery(); + QQmlDebugObjectReference object() const; +private: + friend class QQmlEngineDebug; + friend class QQmlEngineDebugPrivate; + QQmlDebugObjectQuery(QObject *); + QQmlEngineDebug *m_client; + int m_queryId; + QQmlDebugObjectReference m_object; + +}; + +class Q_QML_PRIVATE_EXPORT QQmlDebugExpressionQuery : public QQmlDebugQuery +{ + Q_OBJECT +public: + virtual ~QQmlDebugExpressionQuery(); + QVariant expression() const; + QVariant result() const; +private: + friend class QQmlEngineDebug; + friend class QQmlEngineDebugPrivate; + QQmlDebugExpressionQuery(QObject *); + QQmlEngineDebug *m_client; + int m_queryId; + QVariant m_expr; + QVariant m_result; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QQmlDebugEngineReference) +Q_DECLARE_METATYPE(QQmlDebugObjectReference) +Q_DECLARE_METATYPE(QQmlDebugContextReference) +Q_DECLARE_METATYPE(QQmlDebugPropertyReference) + +QT_END_HEADER + +#endif // QQMLENGINEDEBUG_H diff --git a/src/qml/debugger/qqmlenginedebugservice.cpp b/src/qml/debugger/qqmlenginedebugservice.cpp new file mode 100644 index 0000000000..be2e826bdf --- /dev/null +++ b/src/qml/debugger/qqmlenginedebugservice.cpp @@ -0,0 +1,733 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qqmlenginedebugservice_p.h" + +#include "qqmldebugstatesdelegate_p.h" +#include <private/qqmlboundsignal_p.h> +#include <qqmlengine.h> +#include <private/qqmlmetatype_p.h> +#include <qqmlproperty.h> +#include <private/qqmlproperty_p.h> +#include <private/qqmlbinding_p.h> +#include <private/qqmlcontext_p.h> +#include <private/qqmlwatcher_p.h> +#include <private/qqmlvaluetype_p.h> +#include <private/qqmlvmemetaobject_p.h> +#include <private/qqmlexpression_p.h> + +#include <QtCore/qdebug.h> +#include <QtCore/qmetaobject.h> + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QQmlEngineDebugService, qmlEngineDebugService); + +QQmlEngineDebugService *QQmlEngineDebugService::instance() +{ + return qmlEngineDebugService(); +} + +QQmlEngineDebugService::QQmlEngineDebugService(QObject *parent) + : QQmlDebugService(QLatin1String("QQmlEngine"), 1, parent), + m_watch(new QQmlWatcher(this)), + m_statesDelegate(0) +{ + QObject::connect(m_watch, SIGNAL(propertyChanged(int,int,QMetaProperty,QVariant)), + this, SLOT(propertyChanged(int,int,QMetaProperty,QVariant))); + + registerService(); +} + +QQmlEngineDebugService::~QQmlEngineDebugService() +{ + delete m_statesDelegate; +} + +QDataStream &operator<<(QDataStream &ds, + const QQmlEngineDebugService::QQmlObjectData &data) +{ + ds << data.url << data.lineNumber << data.columnNumber << data.idString + << data.objectName << data.objectType << data.objectId << data.contextId; + return ds; +} + +QDataStream &operator>>(QDataStream &ds, + QQmlEngineDebugService::QQmlObjectData &data) +{ + ds >> data.url >> data.lineNumber >> data.columnNumber >> data.idString + >> data.objectName >> data.objectType >> data.objectId >> data.contextId; + return ds; +} + +QDataStream &operator<<(QDataStream &ds, + const QQmlEngineDebugService::QQmlObjectProperty &data) +{ + ds << (int)data.type << data.name << data.value << data.valueTypeName + << data.binding << data.hasNotifySignal; + return ds; +} + +QDataStream &operator>>(QDataStream &ds, + QQmlEngineDebugService::QQmlObjectProperty &data) +{ + int type; + ds >> type >> data.name >> data.value >> data.valueTypeName + >> data.binding >> data.hasNotifySignal; + data.type = (QQmlEngineDebugService::QQmlObjectProperty::Type)type; + return ds; +} + +static inline bool isSignalPropertyName(const QString &signalName) +{ + // see QmlCompiler::isSignalPropertyName + return signalName.length() >= 3 && signalName.startsWith(QLatin1String("on")) && + signalName.at(2).isLetter() && signalName.at(2).isUpper(); +} + +static bool hasValidSignal(QObject *object, const QString &propertyName) +{ + if (!isSignalPropertyName(propertyName)) + return false; + + QString signalName = propertyName.mid(2); + signalName[0] = signalName.at(0).toLower(); + + int sigIdx = QQmlPropertyPrivate::findSignalByName(object->metaObject(), signalName.toLatin1()).methodIndex(); + + if (sigIdx == -1) + return false; + + return true; +} + +QQmlEngineDebugService::QQmlObjectProperty +QQmlEngineDebugService::propertyData(QObject *obj, int propIdx) +{ + QQmlObjectProperty rv; + + QMetaProperty prop = obj->metaObject()->property(propIdx); + + rv.type = QQmlObjectProperty::Unknown; + rv.valueTypeName = QString::fromUtf8(prop.typeName()); + rv.name = QString::fromUtf8(prop.name()); + rv.hasNotifySignal = prop.hasNotifySignal(); + QQmlAbstractBinding *binding = + QQmlPropertyPrivate::binding(QQmlProperty(obj, rv.name)); + if (binding) + rv.binding = binding->expression(); + + if (QQmlValueTypeFactory::isValueType(prop.userType())) { + rv.type = QQmlObjectProperty::Basic; + } else if (QQmlMetaType::isQObject(prop.userType())) { + rv.type = QQmlObjectProperty::Object; + } else if (QQmlMetaType::isList(prop.userType())) { + rv.type = QQmlObjectProperty::List; + } + + QVariant value; + if (rv.type != QQmlObjectProperty::Unknown && prop.userType() != 0) { + value = prop.read(obj); + } + rv.value = valueContents(value); + + return rv; +} + +QVariant QQmlEngineDebugService::valueContents(const QVariant &value) const +{ + int userType = value.userType(); + + //QObject * is not streamable. + //Convert all such instances to a String value + + if (value.type() == QVariant::List) { + QVariantList contents; + QVariantList list = value.toList(); + int count = list.size(); + for (int i = 0; i < count; i++) + contents << valueContents(list.at(i)); + return contents; + } + + if (value.type() == QVariant::Map) { + QVariantMap contents; + QMapIterator<QString, QVariant> i(value.toMap()); + while (i.hasNext()) { + i.next(); + contents.insert(i.key(), valueContents(i.value())); + } + return contents; + } + + if (QQmlValueTypeFactory::isValueType(userType)) + return value; + + if (QQmlMetaType::isQObject(userType)) { + QObject *o = QQmlMetaType::toQObject(value); + if (o) { + QString name = o->objectName(); + if (name.isEmpty()) + name = QLatin1String("<unnamed object>"); + return name; + } + } + + return QLatin1String("<unknown value>"); +} + +void QQmlEngineDebugService::buildObjectDump(QDataStream &message, + QObject *object, bool recur, bool dumpProperties) +{ + message << objectData(object); + + QObjectList children = object->children(); + + int childrenCount = children.count(); + for (int ii = 0; ii < children.count(); ++ii) { + if (qobject_cast<QQmlContext*>(children[ii]) || QQmlBoundSignal::cast(children[ii])) + --childrenCount; + } + + message << childrenCount << recur; + + QList<QQmlObjectProperty> fakeProperties; + + for (int ii = 0; ii < children.count(); ++ii) { + QObject *child = children.at(ii); + if (qobject_cast<QQmlContext*>(child)) + continue; + QQmlBoundSignal *signal = QQmlBoundSignal::cast(child); + if (signal) { + if (!dumpProperties) + continue; + QQmlObjectProperty prop; + prop.type = QQmlObjectProperty::SignalProperty; + prop.hasNotifySignal = false; + QQmlExpression *expr = signal->expression(); + if (expr) { + prop.value = expr->expression(); + QObject *scope = expr->scopeObject(); + if (scope) { + QString sig = QLatin1String(scope->metaObject()->method(signal->index()).signature()); + int lparen = sig.indexOf(QLatin1Char('(')); + if (lparen >= 0) { + QString methodName = sig.mid(0, lparen); + prop.name = QLatin1String("on") + methodName[0].toUpper() + + methodName.mid(1); + } + } + } + fakeProperties << prop; + } else { + if (recur) + buildObjectDump(message, child, recur, dumpProperties); + else + message << objectData(child); + } + } + + if (!dumpProperties) { + message << 0; + return; + } + + QList<int> propertyIndexes; + for (int ii = 0; ii < object->metaObject()->propertyCount(); ++ii) { + if (object->metaObject()->property(ii).isScriptable()) + propertyIndexes << ii; + } + + message << propertyIndexes.size() + fakeProperties.count(); + + for (int ii = 0; ii < propertyIndexes.size(); ++ii) + message << propertyData(object, propertyIndexes.at(ii)); + + for (int ii = 0; ii < fakeProperties.count(); ++ii) + message << fakeProperties[ii]; +} + +void QQmlEngineDebugService::prepareDeferredObjects(QObject *obj) +{ + qmlExecuteDeferred(obj); + + QObjectList children = obj->children(); + for (int ii = 0; ii < children.count(); ++ii) { + QObject *child = children.at(ii); + prepareDeferredObjects(child); + } + +} + +void QQmlEngineDebugService::buildObjectList(QDataStream &message, QQmlContext *ctxt) +{ + QQmlContextData *p = QQmlContextData::get(ctxt); + + QString ctxtName = ctxt->objectName(); + int ctxtId = QQmlDebugService::idForObject(ctxt); + + message << ctxtName << ctxtId; + + int count = 0; + + QQmlContextData *child = p->childContexts; + while (child) { + ++count; + child = child->nextChild; + } + + message << count; + + child = p->childContexts; + while (child) { + buildObjectList(message, child->asQQmlContext()); + child = child->nextChild; + } + + // Clean deleted objects + QQmlContextPrivate *ctxtPriv = QQmlContextPrivate::get(ctxt); + for (int ii = 0; ii < ctxtPriv->instances.count(); ++ii) { + if (!ctxtPriv->instances.at(ii)) { + ctxtPriv->instances.removeAt(ii); + --ii; + } + } + + message << ctxtPriv->instances.count(); + for (int ii = 0; ii < ctxtPriv->instances.count(); ++ii) { + message << objectData(ctxtPriv->instances.at(ii)); + } +} + +void QQmlEngineDebugService::buildStatesList(QQmlContext *ctxt, bool cleanList) +{ + if (m_statesDelegate) + m_statesDelegate->buildStatesList(ctxt, cleanList); +} + +QQmlEngineDebugService::QQmlObjectData +QQmlEngineDebugService::objectData(QObject *object) +{ + QQmlData *ddata = QQmlData::get(object); + QQmlObjectData rv; + if (ddata && ddata->outerContext) { + rv.url = ddata->outerContext->url; + rv.lineNumber = ddata->lineNumber; + rv.columnNumber = ddata->columnNumber; + } else { + rv.lineNumber = -1; + rv.columnNumber = -1; + } + + QQmlContext *context = qmlContext(object); + if (context) { + QQmlContextData *cdata = QQmlContextData::get(context); + if (cdata) + rv.idString = cdata->findObjectId(object); + } + + rv.objectName = object->objectName(); + rv.objectId = QQmlDebugService::idForObject(object); + rv.contextId = QQmlDebugService::idForObject(qmlContext(object)); + + QQmlType *type = QQmlMetaType::qmlType(object->metaObject()); + if (type) { + QString typeName = type->qmlTypeName(); + int lastSlash = typeName.lastIndexOf(QLatin1Char('/')); + rv.objectType = lastSlash < 0 ? typeName : typeName.mid(lastSlash+1); + } else { + rv.objectType = QString::fromUtf8(object->metaObject()->className()); + int marker = rv.objectType.indexOf(QLatin1String("_QMLTYPE_")); + if (marker != -1) + rv.objectType = rv.objectType.left(marker); + } + + return rv; +} + +void QQmlEngineDebugService::messageReceived(const QByteArray &message) +{ + QMetaObject::invokeMethod(this, "processMessage", Qt::QueuedConnection, Q_ARG(QByteArray, message)); +} + +void QQmlEngineDebugService::processMessage(const QByteArray &message) +{ + QDataStream ds(message); + + QByteArray type; + ds >> type; + + if (type == "LIST_ENGINES") { + int queryId; + ds >> queryId; + + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + rs << QByteArray("LIST_ENGINES_R"); + rs << queryId << m_engines.count(); + + for (int ii = 0; ii < m_engines.count(); ++ii) { + QQmlEngine *engine = m_engines.at(ii); + + QString engineName = engine->objectName(); + int engineId = QQmlDebugService::idForObject(engine); + + rs << engineName << engineId; + } + + sendMessage(reply); + } else if (type == "LIST_OBJECTS") { + int queryId; + int engineId = -1; + ds >> queryId >> engineId; + + QQmlEngine *engine = + qobject_cast<QQmlEngine *>(QQmlDebugService::objectForId(engineId)); + + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + rs << QByteArray("LIST_OBJECTS_R") << queryId; + + if (engine) { + buildObjectList(rs, engine->rootContext()); + buildStatesList(engine->rootContext(), true); + } + + sendMessage(reply); + } else if (type == "FETCH_OBJECT") { + int queryId; + int objectId; + bool recurse; + bool dumpProperties = true; + + ds >> queryId >> objectId >> recurse >> dumpProperties; + + QObject *object = QQmlDebugService::objectForId(objectId); + + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + rs << QByteArray("FETCH_OBJECT_R") << queryId; + + if (object) { + if (recurse) + prepareDeferredObjects(object); + buildObjectDump(rs, object, recurse, dumpProperties); + } + + sendMessage(reply); + } else if (type == "WATCH_OBJECT") { + int queryId; + int objectId; + + ds >> queryId >> objectId; + bool ok = m_watch->addWatch(queryId, objectId); + + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + rs << QByteArray("WATCH_OBJECT_R") << queryId << ok; + + sendMessage(reply); + } else if (type == "WATCH_PROPERTY") { + int queryId; + int objectId; + QByteArray property; + + ds >> queryId >> objectId >> property; + bool ok = m_watch->addWatch(queryId, objectId, property); + + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + rs << QByteArray("WATCH_PROPERTY_R") << queryId << ok; + + sendMessage(reply); + } else if (type == "WATCH_EXPR_OBJECT") { + int queryId; + int debugId; + QString expr; + + ds >> queryId >> debugId >> expr; + bool ok = m_watch->addWatch(queryId, debugId, expr); + + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + rs << QByteArray("WATCH_EXPR_OBJECT_R") << queryId << ok; + sendMessage(reply); + } else if (type == "NO_WATCH") { + int queryId; + + ds >> queryId; + m_watch->removeWatch(queryId); + } else if (type == "EVAL_EXPRESSION") { + int queryId; + int objectId; + QString expr; + + ds >> queryId >> objectId >> expr; + + QObject *object = QQmlDebugService::objectForId(objectId); + QQmlContext *context = qmlContext(object); + QVariant result; + if (object && context) { + QQmlExpression exprObj(context, object, expr); + bool undefined = false; + QVariant value = exprObj.evaluate(&undefined); + if (undefined) + result = QLatin1String("<undefined>"); + else + result = valueContents(value); + } else { + result = QLatin1String("<unknown context>"); + } + + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + rs << QByteArray("EVAL_EXPRESSION_R") << queryId << result; + + sendMessage(reply); + } else if (type == "SET_BINDING") { + int objectId; + QString propertyName; + QVariant expr; + bool isLiteralValue; + QString filename; + int line; + ds >> objectId >> propertyName >> expr >> isLiteralValue; + if (!ds.atEnd()) { // backward compatibility from 2.1, 2.2 + ds >> filename >> line; + } + setBinding(objectId, propertyName, expr, isLiteralValue, filename, line); + } else if (type == "RESET_BINDING") { + int objectId; + QString propertyName; + ds >> objectId >> propertyName; + resetBinding(objectId, propertyName); + } else if (type == "SET_METHOD_BODY") { + int objectId; + QString methodName; + QString methodBody; + ds >> objectId >> methodName >> methodBody; + setMethodBody(objectId, methodName, methodBody); + } +} + +void QQmlEngineDebugService::setBinding(int objectId, + const QString &propertyName, + const QVariant &expression, + bool isLiteralValue, + QString filename, + int line, + int column) +{ + QObject *object = objectForId(objectId); + QQmlContext *context = qmlContext(object); + + if (object && context) { + QQmlProperty property(object, propertyName, context); + if (property.isValid()) { + + bool inBaseState = true; + if (m_statesDelegate) { + m_statesDelegate->updateBinding(context, property, expression, isLiteralValue, + filename, line, column, &inBaseState); + } + + if (inBaseState) { + if (isLiteralValue) { + property.write(expression); + } else if (hasValidSignal(object, propertyName)) { + QQmlExpression *qmlExpression = new QQmlExpression(context, object, expression.toString()); + QQmlPropertyPrivate::setSignalExpression(property, qmlExpression); + qmlExpression->setSourceLocation(filename, line, column); + } else if (property.isProperty()) { + QQmlBinding *binding = new QQmlBinding(expression.toString(), object, context); + binding->setTarget(property); + binding->setSourceLocation(filename, line, column); + binding->setNotifyOnValueChanged(true); + QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::setBinding(property, binding); + if (oldBinding) + oldBinding->destroy(); + binding->update(); + } else { + qWarning() << "QQmlEngineDebugService::setBinding: unable to set property" << propertyName << "on object" << object; + } + } + + } else { + // not a valid property + bool ok = false; + if (m_statesDelegate) + ok = m_statesDelegate->setBindingForInvalidProperty(object, propertyName, expression, isLiteralValue); + if (!ok) + qWarning() << "QQmlEngineDebugService::setBinding: unable to set property" << propertyName << "on object" << object; + } + } +} + +void QQmlEngineDebugService::resetBinding(int objectId, const QString &propertyName) +{ + QObject *object = objectForId(objectId); + QQmlContext *context = qmlContext(object); + + if (object && context) { + if (object->property(propertyName.toLatin1()).isValid()) { + QQmlProperty property(object, propertyName); + QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::binding(property); + if (oldBinding) { + QQmlAbstractBinding *oldBinding = QQmlPropertyPrivate::setBinding(property, 0); + if (oldBinding) + oldBinding->destroy(); + } + if (property.isResettable()) { + // Note: this will reset the property in any case, without regard to states + // Right now almost no QQuickItem has reset methods for its properties (with the + // notable exception of QQuickAnchors), so this is not a big issue + // later on, setBinding does take states into account + property.reset(); + } else { + // overwrite with default value + if (QQmlType *objType = QQmlMetaType::qmlType(object->metaObject())) { + if (QObject *emptyObject = objType->create()) { + if (emptyObject->property(propertyName.toLatin1()).isValid()) { + QVariant defaultValue = QQmlProperty(emptyObject, propertyName).read(); + if (defaultValue.isValid()) { + setBinding(objectId, propertyName, defaultValue, true); + } + } + delete emptyObject; + } + } + } + } else if (hasValidSignal(object, propertyName)) { + QQmlProperty property(object, propertyName, context); + QQmlPropertyPrivate::setSignalExpression(property, 0); + } else { + if (m_statesDelegate) + m_statesDelegate->resetBindingForInvalidProperty(object, propertyName); + } + } +} + +void QQmlEngineDebugService::setMethodBody(int objectId, const QString &method, const QString &body) +{ + QObject *object = objectForId(objectId); + QQmlContext *context = qmlContext(object); + if (!object || !context || !context->engine()) + return; + QQmlContextData *contextData = QQmlContextData::get(context); + if (!contextData) + return; + + QQmlPropertyData dummy; + QQmlPropertyData *prop = + QQmlPropertyCache::property(context->engine(), object, method, dummy); + + if (!prop || !prop->isVMEFunction()) + return; + + QMetaMethod metaMethod = object->metaObject()->method(prop->coreIndex); + QList<QByteArray> paramNames = metaMethod.parameterNames(); + + QString paramStr; + for (int ii = 0; ii < paramNames.count(); ++ii) { + if (ii != 0) paramStr.append(QLatin1String(",")); + paramStr.append(QString::fromUtf8(paramNames.at(ii))); + } + + QString jsfunction = QLatin1String("(function ") + method + QLatin1String("(") + paramStr + + QLatin1String(") {"); + jsfunction += body; + jsfunction += QLatin1String("\n})"); + + QQmlVMEMetaObject *vmeMetaObject = + static_cast<QQmlVMEMetaObject*>(QObjectPrivate::get(object)->metaObject); + Q_ASSERT(vmeMetaObject); // the fact we found the property above should guarentee this + + int lineNumber = vmeMetaObject->vmeMethodLineNumber(prop->coreIndex); + vmeMetaObject->setVmeMethod(prop->coreIndex, QQmlExpressionPrivate::evalFunction(contextData, object, jsfunction, contextData->url.toString(), lineNumber)); +} + +void QQmlEngineDebugService::propertyChanged(int id, int objectId, const QMetaProperty &property, const QVariant &value) +{ + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + + rs << QByteArray("UPDATE_WATCH") << id << objectId << QByteArray(property.name()) << valueContents(value); + + sendMessage(reply); +} + +void QQmlEngineDebugService::addEngine(QQmlEngine *engine) +{ + Q_ASSERT(engine); + Q_ASSERT(!m_engines.contains(engine)); + + m_engines.append(engine); +} + +void QQmlEngineDebugService::remEngine(QQmlEngine *engine) +{ + Q_ASSERT(engine); + Q_ASSERT(m_engines.contains(engine)); + + m_engines.removeAll(engine); +} + +void QQmlEngineDebugService::objectCreated(QQmlEngine *engine, QObject *object) +{ + Q_ASSERT(engine); + Q_ASSERT(m_engines.contains(engine)); + + int engineId = QQmlDebugService::idForObject(engine); + int objectId = QQmlDebugService::idForObject(object); + + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + + rs << QByteArray("OBJECT_CREATED") << engineId << objectId; + sendMessage(reply); +} + +void QQmlEngineDebugService::setStatesDelegate(QQmlDebugStatesDelegate *delegate) +{ + m_statesDelegate = delegate; +} + +QT_END_NAMESPACE diff --git a/src/qml/debugger/qqmlenginedebugservice_p.h b/src/qml/debugger/qqmlenginedebugservice_p.h new file mode 100644 index 0000000000..1a92801fcc --- /dev/null +++ b/src/qml/debugger/qqmlenginedebugservice_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** 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 QQMLENGINEDEBUGSERVICE_P_H +#define QQMLENGINEDEBUGSERVICE_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 <private/qqmldebugservice_p.h> + +#include <QtCore/qurl.h> +#include <QtCore/qvariant.h> + +QT_BEGIN_NAMESPACE + +class QQmlEngine; +class QQmlContext; +class QQmlWatcher; +class QDataStream; +class QQmlDebugStatesDelegate; + +class Q_QML_PRIVATE_EXPORT QQmlEngineDebugService : public QQmlDebugService +{ + Q_OBJECT +public: + QQmlEngineDebugService(QObject * = 0); + ~QQmlEngineDebugService(); + + struct QQmlObjectData { + QUrl url; + int lineNumber; + int columnNumber; + QString idString; + QString objectName; + QString objectType; + int objectId; + int contextId; + }; + + struct QQmlObjectProperty { + enum Type { Unknown, Basic, Object, List, SignalProperty }; + Type type; + QString name; + QVariant value; + QString valueTypeName; + QString binding; + bool hasNotifySignal; + }; + + void addEngine(QQmlEngine *); + void remEngine(QQmlEngine *); + void objectCreated(QQmlEngine *, QObject *); + + void setStatesDelegate(QQmlDebugStatesDelegate *); + + static QQmlEngineDebugService *instance(); + +protected: + virtual void messageReceived(const QByteArray &); + +private Q_SLOTS: + void processMessage(const QByteArray &msg); + void propertyChanged(int id, int objectId, const QMetaProperty &property, const QVariant &value); + +private: + void prepareDeferredObjects(QObject *); + void buildObjectList(QDataStream &, QQmlContext *); + void buildObjectDump(QDataStream &, QObject *, bool, bool); + void buildStatesList(QQmlContext *, bool); + QQmlObjectData objectData(QObject *); + QQmlObjectProperty propertyData(QObject *, int); + QVariant valueContents(const QVariant &defaultValue) const; + void setBinding(int objectId, const QString &propertyName, const QVariant &expression, bool isLiteralValue, QString filename = QString(), int line = -1, int column = 0); + void resetBinding(int objectId, const QString &propertyName); + void setMethodBody(int objectId, const QString &method, const QString &body); + + QList<QQmlEngine *> m_engines; + QQmlWatcher *m_watch; + QQmlDebugStatesDelegate *m_statesDelegate; +}; +Q_QML_PRIVATE_EXPORT QDataStream &operator<<(QDataStream &, const QQmlEngineDebugService::QQmlObjectData &); +Q_QML_PRIVATE_EXPORT QDataStream &operator>>(QDataStream &, QQmlEngineDebugService::QQmlObjectData &); +Q_QML_PRIVATE_EXPORT QDataStream &operator<<(QDataStream &, const QQmlEngineDebugService::QQmlObjectProperty &); +Q_QML_PRIVATE_EXPORT QDataStream &operator>>(QDataStream &, QQmlEngineDebugService::QQmlObjectProperty &); + +QT_END_NAMESPACE + +#endif // QQMLENGINEDEBUGSERVICE_P_H + diff --git a/src/qml/debugger/qqmlinspectorinterface_p.h b/src/qml/debugger/qqmlinspectorinterface_p.h new file mode 100644 index 0000000000..7f52dffa2e --- /dev/null +++ b/src/qml/debugger/qqmlinspectorinterface_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 QQMLINSPECTORINTERFACE_H +#define QQMLINSPECTORINTERFACE_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 <QtQml/qtqmlglobal.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + + +class Q_QML_EXPORT QQmlInspectorInterface +{ +public: + QQmlInspectorInterface() {} + virtual ~QQmlInspectorInterface() {} + + virtual bool canHandleView(QObject *view) = 0; + + virtual void activate(QObject *view) = 0; + virtual void deactivate() = 0; + + virtual void clientMessage(const QByteArray &message) = 0; +}; + +Q_DECLARE_INTERFACE(QQmlInspectorInterface, "com.trolltech.Qt.QQmlInspectorInterface/1.0") + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QQMLINSPECTORINTERFACE_H diff --git a/src/qml/debugger/qqmlinspectorservice.cpp b/src/qml/debugger/qqmlinspectorservice.cpp new file mode 100644 index 0000000000..508d12eefa --- /dev/null +++ b/src/qml/debugger/qqmlinspectorservice.cpp @@ -0,0 +1,186 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qqmlinspectorservice_p.h" +#include "qqmlinspectorinterface_p.h" +#include "qqmldebugserver_p.h" + +#include <private/qqmlglobal_p.h> + +#include <QtCore/QCoreApplication> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QPluginLoader> + +// print detailed information about loading of plugins +DEFINE_BOOL_CONFIG_OPTION(qmlDebugVerbose, QML_DEBUGGER_VERBOSE) + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QQmlInspectorService, serviceInstance) + +QQmlInspectorService::QQmlInspectorService() + : QQmlDebugService(QLatin1String("QQmlObserverMode"), 1) + , m_currentInspectorPlugin(0) +{ + registerService(); +} + +QQmlInspectorService *QQmlInspectorService::instance() +{ + return serviceInstance(); +} + +void QQmlInspectorService::addView(QObject *view) +{ + m_views.append(view); + updateState(); +} + +void QQmlInspectorService::removeView(QObject *view) +{ + m_views.removeAll(view); + updateState(); +} + +void QQmlInspectorService::sendMessage(const QByteArray &message) +{ + if (state() != Enabled) + return; + + QQmlDebugService::sendMessage(message); +} + +void QQmlInspectorService::stateChanged(State /*state*/) +{ + QMetaObject::invokeMethod(this, "updateState", Qt::QueuedConnection); +} + +void QQmlInspectorService::updateState() +{ + if (m_views.isEmpty()) { + if (m_currentInspectorPlugin) { + m_currentInspectorPlugin->deactivate(); + m_currentInspectorPlugin = 0; + } + return; + } + + if (state() == Enabled) { + if (m_inspectorPlugins.isEmpty()) + loadInspectorPlugins(); + + if (m_inspectorPlugins.isEmpty()) { + qWarning() << "QQmlInspector: No plugins found."; + QQmlDebugServer::instance()->removeService(this); + return; + } + + foreach (QQmlInspectorInterface *inspector, m_inspectorPlugins) { + if (inspector->canHandleView(m_views.first())) { + m_currentInspectorPlugin = inspector; + break; + } + } + + if (!m_currentInspectorPlugin) { + qWarning() << "QQmlInspector: No plugin available for view '" << m_views.first()->metaObject()->className() << "'."; + return; + } + m_currentInspectorPlugin->activate(m_views.first()); + } else { + if (m_currentInspectorPlugin) { + m_currentInspectorPlugin->deactivate(); + m_currentInspectorPlugin = 0; + } + } +} + +void QQmlInspectorService::messageReceived(const QByteArray &message) +{ + QMetaObject::invokeMethod(this, "processMessage", Qt::QueuedConnection, Q_ARG(QByteArray, message)); +} + +void QQmlInspectorService::processMessage(const QByteArray &message) +{ + if (m_currentInspectorPlugin) + m_currentInspectorPlugin->clientMessage(message); +} + +void QQmlInspectorService::loadInspectorPlugins() +{ + QStringList pluginCandidates; + const QStringList paths = QCoreApplication::libraryPaths(); + foreach (const QString &libPath, paths) { + const QDir dir(libPath + QLatin1String("/qmltooling")); + if (dir.exists()) + foreach (const QString &pluginPath, dir.entryList(QDir::Files)) + pluginCandidates << dir.absoluteFilePath(pluginPath); + } + + foreach (const QString &pluginPath, pluginCandidates) { + if (qmlDebugVerbose()) + qDebug() << "QQmlInspector: Trying to load plugin " << pluginPath << "..."; + + QPluginLoader loader(pluginPath); + if (!loader.load()) { + if (qmlDebugVerbose()) + qDebug() << "QQmlInspector: Error while loading: " << loader.errorString(); + + continue; + } + + QQmlInspectorInterface *inspector = + qobject_cast<QQmlInspectorInterface*>(loader.instance()); + if (inspector) { + if (qmlDebugVerbose()) + qDebug() << "QQmlInspector: Plugin successfully loaded."; + m_inspectorPlugins << inspector; + } else { + if (qmlDebugVerbose()) + qDebug() << "QQmlInspector: Plugin does not implement interface QQmlInspectorInterface."; + + loader.unload(); + } + } +} + +QT_END_NAMESPACE diff --git a/src/qml/debugger/qqmlinspectorservice_p.h b/src/qml/debugger/qqmlinspectorservice_p.h new file mode 100644 index 0000000000..557dc38aa8 --- /dev/null +++ b/src/qml/debugger/qqmlinspectorservice_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** 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 QQMLINSPECTORSERVICE_H +#define QQMLINSPECTORSERVICE_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 "qqmldebugservice_p.h" + +#include <QtQml/qtqmlglobal.h> +#include <QtCore/QList> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + + +class QQmlInspectorInterface; + +class Q_QML_EXPORT QQmlInspectorService : public QQmlDebugService +{ + Q_OBJECT + +public: + QQmlInspectorService(); + static QQmlInspectorService *instance(); + + void addView(QObject *); + void removeView(QObject *); + + void sendMessage(const QByteArray &message); + +protected: + virtual void stateChanged(State state); + virtual void messageReceived(const QByteArray &); + +private slots: + void processMessage(const QByteArray &message); + void updateState(); + +private: + void loadInspectorPlugins(); + + QList<QObject*> m_views; + QQmlInspectorInterface *m_currentInspectorPlugin; + QList<QQmlInspectorInterface*> m_inspectorPlugins; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QQMLINSPECTORSERVICE_H diff --git a/src/qml/debugger/qqmlprofilerservice.cpp b/src/qml/debugger/qqmlprofilerservice.cpp new file mode 100644 index 0000000000..d6a0307836 --- /dev/null +++ b/src/qml/debugger/qqmlprofilerservice.cpp @@ -0,0 +1,357 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qqmlprofilerservice_p.h" + +#include <QtCore/qdatastream.h> +#include <QtCore/qurl.h> +#include <QtCore/qtimer.h> +#include <QtCore/qthread.h> +#include <QtCore/qcoreapplication.h> + +// this contains QUnifiedTimer +#include <private/qabstractanimation_p.h> + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QQmlProfilerService, profilerInstance) + +QQmlBindingProfiler::QQmlBindingProfiler(const QString &url, int line, int column) +{ + QQmlProfilerService::startRange(QQmlProfilerService::Binding); + QQmlProfilerService::rangeLocation(QQmlProfilerService::Binding, url, line, column); +} + +QQmlBindingProfiler::~QQmlBindingProfiler() +{ + QQmlProfilerService::endRange(QQmlProfilerService::Binding); +} + +void QQmlBindingProfiler::addDetail(const QString &details) +{ + QQmlProfilerService::rangeData(QQmlProfilerService::Binding, details); +} + +// convert to a QByteArray that can be sent to the debug client +// use of QDataStream can skew results +// (see tst_qqmldebugtrace::trace() benchmark) +QByteArray QQmlProfilerData::toByteArray() const +{ + QByteArray data; + //### using QDataStream is relatively expensive + QDataStream ds(&data, QIODevice::WriteOnly); + ds << time << messageType << detailType; + if (messageType == (int)QQmlProfilerService::RangeData) + ds << detailData; + if (messageType == (int)QQmlProfilerService::RangeLocation) + ds << detailData << line << column; + if (messageType == (int)QQmlProfilerService::Event && + detailType == (int)QQmlProfilerService::AnimationFrame) + ds << framerate << animationcount; + return data; +} + +QQmlProfilerService::QQmlProfilerService() + : QQmlDebugService(QLatin1String("CanvasFrameRate"), 1), + m_enabled(false), m_messageReceived(false) +{ + m_timer.start(); + + if (registerService() == Enabled) { + // wait for first message indicating whether to trace or not + while (!m_messageReceived) + waitForMessage(); + + QUnifiedTimer::instance()->registerProfilerCallback( &animationFrame ); + } +} + +QQmlProfilerService::~QQmlProfilerService() +{ +} + +void QQmlProfilerService::initialize() +{ + // just make sure that the service is properly registered + profilerInstance(); +} + +bool QQmlProfilerService::startProfiling() +{ + return profilerInstance()->startProfilingImpl(); +} + +bool QQmlProfilerService::stopProfiling() +{ + return profilerInstance()->stopProfilingImpl(); +} + +void QQmlProfilerService::sendStartedProfilingMessage() +{ + profilerInstance()->sendStartedProfilingMessageImpl(); +} + +void QQmlProfilerService::addEvent(EventType t) +{ + profilerInstance()->addEventImpl(t); +} + +void QQmlProfilerService::startRange(RangeType t) +{ + profilerInstance()->startRangeImpl(t); +} + +void QQmlProfilerService::rangeData(RangeType t, const QString &data) +{ + profilerInstance()->rangeDataImpl(t, data); +} + +void QQmlProfilerService::rangeData(RangeType t, const QUrl &data) +{ + profilerInstance()->rangeDataImpl(t, data); +} + +void QQmlProfilerService::rangeLocation(RangeType t, const QString &fileName, int line, int column) +{ + profilerInstance()->rangeLocationImpl(t, fileName, line, column); +} + +void QQmlProfilerService::rangeLocation(RangeType t, const QUrl &fileName, int line, int column) +{ + profilerInstance()->rangeLocationImpl(t, fileName, line, column); +} + +void QQmlProfilerService::endRange(RangeType t) +{ + profilerInstance()->endRangeImpl(t); +} + +void QQmlProfilerService::animationFrame(qint64 delta) +{ + profilerInstance()->animationFrameImpl(delta); +} + +void QQmlProfilerService::sendProfilingData() +{ + profilerInstance()->sendMessages(); +} + +bool QQmlProfilerService::startProfilingImpl() +{ + bool success = false; + if (!profilingEnabled()) { + setProfilingEnabled(true); + sendStartedProfilingMessageImpl(); + success = true; + } + return success; +} + +bool QQmlProfilerService::stopProfilingImpl() +{ + bool success = false; + if (profilingEnabled()) { + addEventImpl(EndTrace); + setProfilingEnabled(false); + success = true; + } + return success; +} + +void QQmlProfilerService::sendStartedProfilingMessageImpl() +{ + if (!QQmlDebugService::isDebuggingEnabled() || !m_enabled) + return; + + QQmlProfilerData ed = {m_timer.nsecsElapsed(), (int)Event, (int)StartTrace, QString(), -1, -1, 0, 0}; + QQmlDebugService::sendMessage(ed.toByteArray()); +} + +void QQmlProfilerService::addEventImpl(EventType event) +{ + if (!QQmlDebugService::isDebuggingEnabled() || !m_enabled) + return; + + QQmlProfilerData ed = {m_timer.nsecsElapsed(), (int)Event, (int)event, QString(), -1, -1, 0, 0}; + processMessage(ed); +} + +void QQmlProfilerService::startRangeImpl(RangeType range) +{ + if (!QQmlDebugService::isDebuggingEnabled() || !m_enabled) + return; + + QQmlProfilerData rd = {m_timer.nsecsElapsed(), (int)RangeStart, (int)range, QString(), -1, -1, 0, 0}; + processMessage(rd); +} + +void QQmlProfilerService::rangeDataImpl(RangeType range, const QString &rData) +{ + if (!QQmlDebugService::isDebuggingEnabled() || !m_enabled) + return; + + QQmlProfilerData rd = {m_timer.nsecsElapsed(), (int)RangeData, (int)range, rData, -1, -1, 0, 0}; + processMessage(rd); +} + +void QQmlProfilerService::rangeDataImpl(RangeType range, const QUrl &rData) +{ + if (!QQmlDebugService::isDebuggingEnabled() || !m_enabled) + return; + + QQmlProfilerData rd = {m_timer.nsecsElapsed(), (int)RangeData, (int)range, rData.toString(QUrl::FormattingOption(0x100)), -1, -1, 0, 0}; + processMessage(rd); +} + +void QQmlProfilerService::rangeLocationImpl(RangeType range, const QString &fileName, int line, int column) +{ + if (!QQmlDebugService::isDebuggingEnabled() || !m_enabled) + return; + + QQmlProfilerData rd = {m_timer.nsecsElapsed(), (int)RangeLocation, (int)range, fileName, line, column, 0, 0}; + processMessage(rd); +} + +void QQmlProfilerService::rangeLocationImpl(RangeType range, const QUrl &fileName, int line, int column) +{ + if (!QQmlDebugService::isDebuggingEnabled() || !m_enabled) + return; + + QQmlProfilerData rd = {m_timer.nsecsElapsed(), (int)RangeLocation, (int)range, fileName.toString(QUrl::FormattingOption(0x100)), line, column, 0, 0}; + processMessage(rd); +} + +void QQmlProfilerService::endRangeImpl(RangeType range) +{ + if (!QQmlDebugService::isDebuggingEnabled() || !m_enabled) + return; + + QQmlProfilerData rd = {m_timer.nsecsElapsed(), (int)RangeEnd, (int)range, QString(), -1, -1, 0, 0}; + processMessage(rd); +} + +void QQmlProfilerService::animationFrameImpl(qint64 delta) +{ + Q_ASSERT(QQmlDebugService::isDebuggingEnabled()); + if (!m_enabled) + return; + + int animCount = QUnifiedTimer::instance()->runningAnimationCount(); + + if (animCount > 0 && delta > 0) { + // trim fps to integer + int fps = 1000 / delta; + QQmlProfilerData ed = {m_timer.nsecsElapsed(), (int)Event, (int)AnimationFrame, QString(), -1, -1, fps, animCount}; + processMessage(ed); + } +} + +/* + Either send the message directly, or queue up + a list of messages to send later (via sendMessages) +*/ +void QQmlProfilerService::processMessage(const QQmlProfilerData &message) +{ + QMutexLocker locker(&m_mutex); + m_data.append(message); +} + +bool QQmlProfilerService::profilingEnabled() +{ + return m_enabled; +} + +void QQmlProfilerService::setProfilingEnabled(bool enable) +{ + m_enabled = enable; +} + +/* + Send the messages queued up by processMessage +*/ +void QQmlProfilerService::sendMessages() +{ + QMutexLocker locker(&m_mutex); + QList<QByteArray> messages; + for (int i = 0; i < m_data.count(); ++i) + messages << m_data.at(i).toByteArray(); + m_data.clear(); + + //indicate completion + QByteArray data; + QDataStream ds(&data, QIODevice::WriteOnly); + ds << (qint64)-1 << (int)Complete; + messages << data; + + QQmlDebugService::sendMessages(messages); +} + +void QQmlProfilerService::stateAboutToBeChanged(QQmlDebugService::State newState) +{ + if (state() == newState) + return; + + if (state() == Enabled + && m_enabled) { + stopProfilingImpl(); + sendMessages(); + } +} + +void QQmlProfilerService::messageReceived(const QByteArray &message) +{ + QByteArray rwData = message; + QDataStream stream(&rwData, QIODevice::ReadOnly); + + bool enabled; + stream >> enabled; + + m_messageReceived = true; + + if (enabled) { + startProfilingImpl(); + } else { + if (stopProfilingImpl()) + sendMessages(); + } +} + +QT_END_NAMESPACE diff --git a/src/qml/debugger/qqmlprofilerservice_p.h b/src/qml/debugger/qqmlprofilerservice_p.h new file mode 100644 index 0000000000..a74ce10c74 --- /dev/null +++ b/src/qml/debugger/qqmlprofilerservice_p.h @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** 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 QQMLPROFILERSERVICE_P_H +#define QQMLPROFILERSERVICE_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 <private/qqmldebugservice_p.h> +#include <QtCore/qelapsedtimer.h> +#include <QtCore/qmutex.h> +#include <QtCore/qvector.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +struct Q_AUTOTEST_EXPORT QQmlProfilerData +{ + qint64 time; + int messageType; + int detailType; + + //### + QString detailData; //used by RangeData and RangeLocation + int line; //used by RangeLocation + int column; //used by RangeLocation + int framerate; //used by animation events + int animationcount; //used by animation events + + QByteArray toByteArray() const; +}; + +Q_DECLARE_TYPEINFO(QQmlProfilerData, Q_MOVABLE_TYPE); + +class QUrl; +class QQmlEngine; + +// RAII +class Q_AUTOTEST_EXPORT QQmlBindingProfiler { +public: + QQmlBindingProfiler(const QString &url, int line, int column); + ~QQmlBindingProfiler(); + void addDetail(const QString &details); +}; + +class Q_QML_EXPORT QQmlProfilerService : public QQmlDebugService +{ +public: + enum Message { + Event, + RangeStart, + RangeData, + RangeLocation, + RangeEnd, + Complete, // end of transmission + + MaximumMessage + }; + + enum EventType { + FramePaint, + Mouse, + Key, + AnimationFrame, + EndTrace, + StartTrace, + + MaximumEventType + }; + + enum RangeType { + Painting, + Compiling, + Creating, + Binding, //running a binding + HandlingSignal, //running a signal handler + + MaximumRangeType + }; + + static void initialize(); + + static bool startProfiling(); + static bool stopProfiling(); + static void sendStartedProfilingMessage(); + static void addEvent(EventType); + static void startRange(RangeType); + static void rangeData(RangeType, const QString &); + static void rangeData(RangeType, const QUrl &); + static void rangeLocation(RangeType, const QString &, int, int); + static void rangeLocation(RangeType, const QUrl &, int, int); + static void endRange(RangeType); + static void animationFrame(qint64); + + static void sendProfilingData(); + + QQmlProfilerService(); + ~QQmlProfilerService(); + +protected: + virtual void stateAboutToBeChanged(State state); + virtual void messageReceived(const QByteArray &); + +private: + bool startProfilingImpl(); + bool stopProfilingImpl(); + void sendStartedProfilingMessageImpl(); + void addEventImpl(EventType); + void startRangeImpl(RangeType); + void rangeDataImpl(RangeType, const QString &); + void rangeDataImpl(RangeType, const QUrl &); + void rangeLocationImpl(RangeType, const QString &, int, int); + void rangeLocationImpl(RangeType, const QUrl &, int, int); + void endRangeImpl(RangeType); + void animationFrameImpl(qint64); + + bool profilingEnabled(); + void setProfilingEnabled(bool enable); + void sendMessages(); + void processMessage(const QQmlProfilerData &); + +private: + QElapsedTimer m_timer; + bool m_enabled; + bool m_messageReceived; + QVector<QQmlProfilerData> m_data; + QMutex m_mutex; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QQMLPROFILERSERVICE_P_H + diff --git a/src/qml/debugger/qv8debugservice.cpp b/src/qml/debugger/qv8debugservice.cpp new file mode 100644 index 0000000000..ee60bff742 --- /dev/null +++ b/src/qml/debugger/qv8debugservice.cpp @@ -0,0 +1,294 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qv8debugservice_p.h" +#include "qqmldebugservice_p_p.h" +#include <private/qjsconverter_impl_p.h> +#include <private/qv8engine_p.h> + +#include <QtCore/QHash> +#include <QtCore/QFileInfo> +#include <QtCore/QMutex> + +//V8 DEBUG SERVICE PROTOCOL +// <HEADER><COMMAND><DATA> +// <HEADER> : "V8DEBUG" +// <COMMAND> : ["connect", "disconnect", "interrupt", +// "v8request", "v8message", "breakonsignal", +// "breakaftercompile"] +// <DATA> : connect, disconnect, interrupt: empty +// v8request, v8message: <JSONrequest_string> +// breakonsignal: <signalname_string><enabled_bool> +// breakaftercompile: <enabled_bool> + +const char *V8_DEBUGGER_KEY_VERSION = "version"; +const char *V8_DEBUGGER_KEY_CONNECT = "connect"; +const char *V8_DEBUGGER_KEY_INTERRUPT = "interrupt"; +const char *V8_DEBUGGER_KEY_DISCONNECT = "disconnect"; +const char *V8_DEBUGGER_KEY_REQUEST = "v8request"; +const char *V8_DEBUGGER_KEY_V8MESSAGE = "v8message"; +const char *V8_DEBUGGER_KEY_BREAK_ON_SIGNAL = "breakonsignal"; +const char *V8_DEBUGGER_KEY_BREAK_AFTER_COMPILE = "breakaftercompile"; + +QT_BEGIN_NAMESPACE + +struct SignalHandlerData +{ + QString functionName; + bool enabled; +}; + +Q_GLOBAL_STATIC(QV8DebugService, v8ServiceInstance) + +// DebugMessageHandler will call back already when the QV8DebugService constructor is +// running, we therefore need a plain pointer. +static QV8DebugService *v8ServiceInstancePtr = 0; + +void DebugMessageDispatchHandler() +{ + QMetaObject::invokeMethod(v8ServiceInstancePtr, "processDebugMessages", Qt::QueuedConnection); +} + +void DebugMessageHandler(const v8::Debug::Message& message) +{ + v8::DebugEvent event = message.GetEvent(); + + if (event != v8::Break && event != v8::Exception && + event != v8::AfterCompile && event != v8::BeforeCompile) + return; + v8ServiceInstancePtr->debugMessageHandler(QJSConverter::toString(message.GetJSON()), event); +} + +class QV8DebugServicePrivate : public QQmlDebugServicePrivate +{ +public: + QV8DebugServicePrivate() + : connectReceived(false) + , breakAfterCompile(false) + , engine(0) + { + } + + void initializeDebuggerThread(); + + static QByteArray packMessage(const QString &type, const QString &message = QString()); + + bool connectReceived; + bool breakAfterCompile; + QMutex initializeMutex; + QStringList breakOnSignals; + const QV8Engine *engine; +}; + +QV8DebugService::QV8DebugService(QObject *parent) + : QQmlDebugService(*(new QV8DebugServicePrivate()), + QLatin1String("V8Debugger"), 2, parent) +{ + Q_D(QV8DebugService); + v8ServiceInstancePtr = this; + // wait for stateChanged() -> initialize() + d->initializeMutex.lock(); + if (registerService() == Enabled) { + init(); + // ,block mode, client attached + while (!d->connectReceived) { + waitForMessage(); + } + } else { + d->initializeMutex.unlock(); + } +} + +QV8DebugService::~QV8DebugService() +{ +} + +QV8DebugService *QV8DebugService::instance() +{ + return v8ServiceInstance(); +} + +void QV8DebugService::initialize(const QV8Engine *engine) +{ + // just make sure that the service is properly registered + v8ServiceInstance()->setEngine(engine); +} + +void QV8DebugService::setEngine(const QV8Engine *engine) +{ + Q_D(QV8DebugService); + + d->engine = engine; +} + +void QV8DebugService::debugMessageHandler(const QString &message, const v8::DebugEvent &event) +{ + Q_D(QV8DebugService); + sendMessage(QV8DebugServicePrivate::packMessage(QLatin1String(V8_DEBUGGER_KEY_V8MESSAGE), message)); + if (event == v8::AfterCompile && d->breakAfterCompile) + scheduledDebugBreak(true); +} + +void QV8DebugService::signalEmitted(const QString &signal) +{ + //This function is only called by QQmlBoundSignal + //only if there is a slot connected to the signal. Hence, there + //is no need for additional check. + Q_D(QV8DebugService); + + //Parse just the name and remove the class info + //Normalize to Lower case. + QString signalName = signal.left(signal.indexOf(QLatin1String("("))).toLower(); + + foreach (const QString &signal, d->breakOnSignals) { + if (signal == signalName) { + scheduledDebugBreak(true); + break; + } + } +} + +// executed in the gui thread +void QV8DebugService::init() +{ + Q_D(QV8DebugService); + v8::Debug::SetMessageHandler2(DebugMessageHandler); + v8::Debug::SetDebugMessageDispatchHandler(DebugMessageDispatchHandler); + d->initializeMutex.unlock(); +} + +// executed in the gui thread +void QV8DebugService::scheduledDebugBreak(bool schedule) +{ + if (schedule) + v8::Debug::DebugBreak(); + else + v8::Debug::CancelDebugBreak(); +} + +// executed in the debugger thread +void QV8DebugService::stateChanged(QQmlDebugService::State newState) +{ + Q_D(QV8DebugService); + if (newState == Enabled) { + // execute in GUI thread + d->initializeMutex.lock(); + QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); + } +} + +// executed in the debugger thread +void QV8DebugService::messageReceived(const QByteArray &message) +{ + Q_D(QV8DebugService); + + QDataStream ds(message); + QByteArray header; + ds >> header; + + if (header == "V8DEBUG") { + QByteArray command; + QByteArray data; + ds >> command >> data; + + if (command == V8_DEBUGGER_KEY_CONNECT) { + QMutexLocker locker(&d->initializeMutex); + d->connectReceived = true; + sendMessage(QV8DebugServicePrivate::packMessage(QLatin1String(V8_DEBUGGER_KEY_CONNECT))); + + } else if (command == V8_DEBUGGER_KEY_INTERRUPT) { + // break has to be executed in gui thread + QMetaObject::invokeMethod(this, "scheduledDebugBreak", Qt::QueuedConnection, Q_ARG(bool, true)); + sendMessage(QV8DebugServicePrivate::packMessage(QLatin1String(V8_DEBUGGER_KEY_INTERRUPT))); + + } else if (command == V8_DEBUGGER_KEY_DISCONNECT) { + // cancel break has to be executed in gui thread + QMetaObject::invokeMethod(this, "scheduledDebugBreak", Qt::QueuedConnection, Q_ARG(bool, false)); + sendDebugMessage(QString::fromUtf8(data)); + + } else if (command == V8_DEBUGGER_KEY_REQUEST) { + sendDebugMessage(QString::fromUtf8(data)); + + } else if (command == V8_DEBUGGER_KEY_BREAK_ON_SIGNAL) { + QDataStream rs(data); + QByteArray signal; + bool enabled; + rs >> signal >> enabled; + //Normalize to lower case. + QString signalName(QString::fromUtf8(signal).toLower()); + if (enabled) + d->breakOnSignals.append(signalName); + else + d->breakOnSignals.removeOne(signalName); + sendMessage(QV8DebugServicePrivate::packMessage(QLatin1String(V8_DEBUGGER_KEY_BREAK_ON_SIGNAL))); + + } else if (command == V8_DEBUGGER_KEY_BREAK_AFTER_COMPILE) { + QDataStream rs(data); + rs >> d->breakAfterCompile; + sendMessage(QV8DebugServicePrivate::packMessage(QLatin1String(V8_DEBUGGER_KEY_BREAK_AFTER_COMPILE))); + + } + } +} + +void QV8DebugService::sendDebugMessage(const QString &message) +{ + v8::Debug::SendCommand(message.utf16(), message.size()); +} + +void QV8DebugService::processDebugMessages() +{ + Q_D(QV8DebugService); + v8::HandleScope handleScope; + v8::Context::Scope contextScope(d->engine->context()); + v8::Debug::ProcessDebugMessages(); +} + +QByteArray QV8DebugServicePrivate::packMessage(const QString &type, const QString &message) +{ + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + QByteArray cmd("V8DEBUG"); + rs << cmd << type.toUtf8() << message.toUtf8(); + return reply; +} + +QT_END_NAMESPACE diff --git a/src/qml/debugger/qv8debugservice_p.h b/src/qml/debugger/qv8debugservice_p.h new file mode 100644 index 0000000000..8ff4adc778 --- /dev/null +++ b/src/qml/debugger/qv8debugservice_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** 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 QV8DEBUGSERVICE_P_H +#define QV8DEBUGSERVICE_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 "qqmldebugservice_p.h" +#include <private/qv8debug_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + + +class QV8Engine; +class QV8DebugServicePrivate; + +class QV8DebugService : public QQmlDebugService +{ + Q_OBJECT +public: + QV8DebugService(QObject *parent = 0); + ~QV8DebugService(); + + static QV8DebugService *instance(); + static void initialize(const QV8Engine *engine); + + void debugMessageHandler(const QString &message, const v8::DebugEvent &event); + + void signalEmitted(const QString &signal); + +public slots: + void processDebugMessages(); + +private slots: + void scheduledDebugBreak(bool schedule); + void sendDebugMessage(const QString &message); + void init(); + +protected: + void stateChanged(State newState); + void messageReceived(const QByteArray &); + +private: + void setEngine(const QV8Engine *engine); + +private: + Q_DISABLE_COPY(QV8DebugService) + Q_DECLARE_PRIVATE(QV8DebugService) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QV8DEBUGSERVICE_P_H diff --git a/src/qml/debugger/qv8profilerservice.cpp b/src/qml/debugger/qv8profilerservice.cpp new file mode 100644 index 0000000000..eba8b0feef --- /dev/null +++ b/src/qml/debugger/qv8profilerservice.cpp @@ -0,0 +1,287 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qv8profilerservice_p.h" +#include "qqmldebugservice_p_p.h" +#include "private/qjsconverter_impl_p.h" +#include <private/qv8profiler_p.h> + +#include <QtCore/QHash> + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QV8ProfilerService, v8ProfilerInstance) + +class DebugServiceOutputStream : public v8::OutputStream +{ + QQmlDebugService &_service; +public: + DebugServiceOutputStream(QQmlDebugService &service) + : v8::OutputStream(), + _service(service) {} + void EndOfStream() {} + WriteResult WriteAsciiChunk(char *rawData, int size) + { + QByteArray data; + QDataStream ds(&data, QIODevice::WriteOnly); + ds << QV8ProfilerService::V8SnapshotChunk << QByteArray(rawData, size); + _service.sendMessage(data); + return kContinue; + } +}; + +// convert to a QByteArray that can be sent to the debug client +QByteArray QV8ProfilerData::toByteArray() const +{ + QByteArray data; + //### using QDataStream is relatively expensive + QDataStream ds(&data, QIODevice::WriteOnly); + ds << messageType << filename << functionname << lineNumber << totalTime << selfTime << treeLevel; + + return data; +} + +class QV8ProfilerServicePrivate : public QQmlDebugServicePrivate +{ + Q_DECLARE_PUBLIC(QV8ProfilerService) + +public: + QV8ProfilerServicePrivate() + :initialized(false) + { + } + + void takeSnapshot(v8::HeapSnapshot::Type); + + void printProfileTree(const v8::CpuProfileNode *node, int level = 0); + void sendMessages(); + + QList<QV8ProfilerData> m_data; + + bool initialized; + QList<QString> m_ongoing; +}; + +QV8ProfilerService::QV8ProfilerService(QObject *parent) + : QQmlDebugService(*(new QV8ProfilerServicePrivate()), QLatin1String("V8Profiler"), 1, parent) +{ + Q_D(QV8ProfilerService); + + if (registerService() == Enabled) { + // ,block mode, client attached + while (!d->initialized) + waitForMessage(); + } +} + +QV8ProfilerService::~QV8ProfilerService() +{ +} + +QV8ProfilerService *QV8ProfilerService::instance() +{ + return v8ProfilerInstance(); +} + +void QV8ProfilerService::initialize() +{ + // just make sure that the service is properly registered + v8ProfilerInstance(); +} + +void QV8ProfilerService::stateAboutToBeChanged(QQmlDebugService::State newState) +{ + Q_D(QV8ProfilerService); + + if (state() == newState) + return; + + if (state() == Enabled) { + foreach (const QString &title, d->m_ongoing) + QMetaObject::invokeMethod(this, "stopProfiling", Qt::QueuedConnection, Q_ARG(QString, title)); + sendProfilingData(); + } +} + +void QV8ProfilerService::messageReceived(const QByteArray &message) +{ + Q_D(QV8ProfilerService); + + QDataStream ds(message); + QByteArray command; + QByteArray option; + QByteArray title; + ds >> command >> option; + + if (command == "V8PROFILER") { + ds >> title; + QString titleStr = QString::fromUtf8(title); + if (option == "start") { + QMetaObject::invokeMethod(this, "startProfiling", Qt::QueuedConnection, Q_ARG(QString, titleStr)); + } else if (option == "stop" && d->initialized) { + QMetaObject::invokeMethod(this, "stopProfiling", Qt::QueuedConnection, Q_ARG(QString, titleStr)); + QMetaObject::invokeMethod(this, "sendProfilingData", Qt::QueuedConnection); + } + d->initialized = true; + } + + if (command == "V8SNAPSHOT") { + if (option == "full") + QMetaObject::invokeMethod(this, "takeSnapshot", Qt::QueuedConnection); + else if (option == "delete") { + QMetaObject::invokeMethod(this, "deleteSnapshots", Qt::QueuedConnection); + } + } + + QQmlDebugService::messageReceived(message); +} + +void QV8ProfilerService::startProfiling(const QString &title) +{ + Q_D(QV8ProfilerService); + // Start Profiling + + if (d->m_ongoing.contains(title)) + return; + + v8::HandleScope handle_scope; + v8::Handle<v8::String> v8title = v8::String::New(reinterpret_cast<const uint16_t*>(title.data()), title.size()); + v8::CpuProfiler::StartProfiling(v8title); + + d->m_ongoing.append(title); +} + +void QV8ProfilerService::stopProfiling(const QString &title) +{ + Q_D(QV8ProfilerService); + // Stop profiling + + if (!d->m_ongoing.contains(title)) + return; + d->m_ongoing.removeOne(title); + + v8::HandleScope handle_scope; + v8::Handle<v8::String> v8title = v8::String::New(reinterpret_cast<const uint16_t*>(title.data()), title.size()); + const v8::CpuProfile *cpuProfile = v8::CpuProfiler::StopProfiling(v8title); + if (cpuProfile) { + // can happen at start + const v8::CpuProfileNode *rootNode = cpuProfile->GetTopDownRoot(); + d->printProfileTree(rootNode); + } +} + +void QV8ProfilerService::takeSnapshot() +{ + Q_D(QV8ProfilerService); + d->takeSnapshot(v8::HeapSnapshot::kFull); +} + +void QV8ProfilerService::deleteSnapshots() +{ + v8::HeapProfiler::DeleteAllSnapshots(); +} + +void QV8ProfilerService::sendProfilingData() +{ + Q_D(QV8ProfilerService); + // Send messages to client + d->sendMessages(); +} + +void QV8ProfilerServicePrivate::printProfileTree(const v8::CpuProfileNode *node, int level) +{ + for (int index = 0 ; index < node->GetChildrenCount() ; index++) { + const v8::CpuProfileNode* childNode = node->GetChild(index); + QString scriptResourceName = QJSConverter::toString(childNode->GetScriptResourceName()); + if (scriptResourceName.length() > 0) { + + QV8ProfilerData rd = {(int)QV8ProfilerService::V8Entry, scriptResourceName, + QJSConverter::toString(childNode->GetFunctionName()), + childNode->GetLineNumber(), childNode->GetTotalTime(), childNode->GetSelfTime(), level}; + m_data.append(rd); + + // different nodes might have common children: fix at client side + if (childNode->GetChildrenCount() > 0) { + printProfileTree(childNode, level+1); + } + } + } +} + +void QV8ProfilerServicePrivate::takeSnapshot(v8::HeapSnapshot::Type snapshotType) +{ + Q_Q(QV8ProfilerService); + + v8::HandleScope scope; + v8::Local<v8::String> title = v8::String::New(""); + + DebugServiceOutputStream outputStream(*q); + const v8::HeapSnapshot *snapshot = v8::HeapProfiler::TakeSnapshot(title, snapshotType); + snapshot->Serialize(&outputStream, v8::HeapSnapshot::kJSON); + + //indicate completion + QByteArray data; + QDataStream ds(&data, QIODevice::WriteOnly); + ds << (int)QV8ProfilerService::V8SnapshotComplete; + + q->sendMessage(data); +} + +void QV8ProfilerServicePrivate::sendMessages() +{ + Q_Q(QV8ProfilerService); + + QList<QByteArray> messages; + for (int i = 0; i < m_data.count(); ++i) + messages << m_data.at(i).toByteArray(); + q->sendMessages(messages); + m_data.clear(); + + //indicate completion + QByteArray data; + QDataStream ds(&data, QIODevice::WriteOnly); + ds << (int)QV8ProfilerService::V8Complete; + + q->sendMessage(data); +} + + +QT_END_NAMESPACE diff --git a/src/qml/debugger/qv8profilerservice_p.h b/src/qml/debugger/qv8profilerservice_p.h new file mode 100644 index 0000000000..d408d9ed0e --- /dev/null +++ b/src/qml/debugger/qv8profilerservice_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** 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 QV8PROFILERSERVICE_P_H +#define QV8PROFILERSERVICE_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 <private/qqmldebugservice_p.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + + +struct Q_AUTOTEST_EXPORT QV8ProfilerData +{ + int messageType; + QString filename; + QString functionname; + int lineNumber; + double totalTime; + double selfTime; + int treeLevel; + + QByteArray toByteArray() const; +}; + +class QQmlEngine; +class QV8ProfilerServicePrivate; + +class Q_AUTOTEST_EXPORT QV8ProfilerService : public QQmlDebugService +{ + Q_OBJECT +public: + enum MessageType { + V8Entry, + V8Complete, + V8SnapshotChunk, + V8SnapshotComplete, + + V8MaximumMessage + }; + + QV8ProfilerService(QObject *parent = 0); + ~QV8ProfilerService(); + + static QV8ProfilerService *instance(); + static void initialize(); + +public slots: + void startProfiling(const QString &title); + void stopProfiling(const QString &title); + void takeSnapshot(); + void deleteSnapshots(); + + void sendProfilingData(); + +protected: + void stateAboutToBeChanged(State state); + void messageReceived(const QByteArray &); + +private: + Q_DISABLE_COPY(QV8ProfilerService) + Q_DECLARE_PRIVATE(QV8ProfilerService) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QV8PROFILERSERVICE_P_H |