diff options
Diffstat (limited to 'src/plugins')
66 files changed, 7782 insertions, 319 deletions
diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro index 664a457608..273407c19d 100644 --- a/src/plugins/plugins.pro +++ b/src/plugins/plugins.pro @@ -1,2 +1,2 @@ TEMPLATE = subdirs -SUBDIRS += qmltooling +!contains(QT_CONFIG, no-qml-debug):SUBDIRS += qmltooling diff --git a/src/plugins/qmltooling/qmldbg_debugger/qdebugmessageservice.cpp b/src/plugins/qmltooling/qmldbg_debugger/qdebugmessageservice.cpp new file mode 100644 index 0000000000..6bccec08b1 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qdebugmessageservice.cpp @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdebugmessageservice.h" +#include <private/qqmldebugconnector_p.h> + +#include <QDataStream> + +QT_BEGIN_NAMESPACE + +const QString QDebugMessageService::s_key = QStringLiteral("DebugMessages"); + +void DebugMessageHandler(QtMsgType type, const QMessageLogContext &ctxt, + const QString &buf) +{ + QQmlDebugConnector::service<QDebugMessageService>()->sendDebugMessage(type, ctxt, buf); +} + +QDebugMessageService::QDebugMessageService(QObject *parent) : + QQmlDebugService(s_key, 2, parent), oldMsgHandler(0), + prevState(QQmlDebugService::NotConnected) +{ + // don't execute stateChanged() in parallel + QMutexLocker lock(&initMutex); + if (state() == Enabled) { + oldMsgHandler = qInstallMessageHandler(DebugMessageHandler); + prevState = Enabled; + } +} + +void QDebugMessageService::sendDebugMessage(QtMsgType type, + const QMessageLogContext &ctxt, + const QString &buf) +{ + //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; + QQmlDebugStream ws(&message, QIODevice::WriteOnly); + ws << QByteArray("MESSAGE") << type << buf.toUtf8(); + ws << QString::fromLatin1(ctxt.file).toUtf8(); + ws << ctxt.line << QString::fromLatin1(ctxt.function).toUtf8(); + + emit messageToClient(name(), message); + if (oldMsgHandler) + (*oldMsgHandler)(type, ctxt, buf); +} + +void QDebugMessageService::stateChanged(State state) +{ + QMutexLocker lock(&initMutex); + + if (state != Enabled && prevState == Enabled) { + QtMessageHandler handler = qInstallMessageHandler(oldMsgHandler); + // has our handler been overwritten in between? + if (handler != DebugMessageHandler) + qInstallMessageHandler(handler); + + } else if (state == Enabled && prevState != Enabled) { + oldMsgHandler = qInstallMessageHandler(DebugMessageHandler); + } + + prevState = state; +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.h b/src/plugins/qmltooling/qmldbg_debugger/qdebugmessageservice.h index 942fb6e12c..c0dc41bcd0 100644 --- a/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.h +++ b/src/plugins/qmltooling/qmldbg_debugger/qdebugmessageservice.h @@ -31,47 +31,52 @@ ** ****************************************************************************/ -#ifndef QTCPSERVERCONNECTION_H -#define QTCPSERVERCONNECTION_H +#ifndef QDEBUGMESSAGESERVICE_H +#define QDEBUGMESSAGESERVICE_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. +// + +#include <private/qqmldebugservice_p.h> + +#include <QtCore/qlogging.h> +#include <QtCore/qmutex.h> QT_BEGIN_NAMESPACE -class QQmlDebugServer; -class QTcpServerConnectionPrivate; -class QTcpServerConnection : public QObject, public QQmlDebugServerConnection +class QDebugMessageServicePrivate; + +class QDebugMessageService : public QQmlDebugService { Q_OBJECT - Q_DECLARE_PRIVATE(QTcpServerConnection) - Q_DISABLE_COPY(QTcpServerConnection) - Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlDebugServerConnection") - Q_INTERFACES(QQmlDebugServerConnection) - public: - QTcpServerConnection(); - ~QTcpServerConnection(); - - void setServer(QQmlDebugServer *server); - bool setPortRange(int portFrom, int portTo, bool bock, const QString &hostaddress); + QDebugMessageService(QObject *parent = 0); - bool isConnected() const; - void send(const QList<QByteArray> &messages); - void disconnect(); - bool waitForMessage(); + void sendDebugMessage(QtMsgType type, const QMessageLogContext &ctxt, + const QString &buf); - bool listen(); - void waitForConnection(); +protected: + static const QString s_key; -private Q_SLOTS: - void readyRead(); - void newConnection(); - void invalidPacket(); + void stateChanged(State); private: - QTcpServerConnectionPrivate *d_ptr; + friend class QQmlDebugConnector; + friend class QQmlDebuggerServiceFactory; + + QtMessageHandler oldMsgHandler; + QQmlDebugService::State prevState; + QMutex initMutex; }; QT_END_NAMESPACE -#endif // QTCPSERVERCONNECTION_H +#endif // QDEBUGMESSAGESERVICE_H diff --git a/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro b/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro new file mode 100644 index 0000000000..8d1a54e9e4 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro @@ -0,0 +1,34 @@ +TARGET = qmldbg_debugger +QT = qml-private core-private + +PLUGIN_TYPE = qmltooling +PLUGIN_CLASS_NAME = QQmlDebuggerServiceFactory +load(qt_plugin) + +SOURCES += \ + $$PWD/qdebugmessageservice.cpp \ + $$PWD/qqmldebuggerservicefactory.cpp \ + $$PWD/qqmlenginedebugservice.cpp \ + $$PWD/qqmlnativedebugservice.cpp \ + $$PWD/qqmlwatcher.cpp \ + $$PWD/qv4debugservice.cpp \ + $$PWD/qv4debuggeragent.cpp \ + $$PWD/qv4datacollector.cpp + +HEADERS += \ + $$PWD/../shared/qqmlconfigurabledebugservice.h \ + $$PWD/qdebugmessageservice.h \ + $$PWD/qqmldebuggerservicefactory.h \ + $$PWD/qqmlenginedebugservice.h \ + $$PWD/qqmlnativedebugservice.h \ + $$PWD/qqmlwatcher.h \ + $$PWD/qv4debugservice.h \ + $$PWD/qv4debuggeragent.h \ + $$PWD/qv4datacollector.h + +INCLUDEPATH += $$PWD \ + $$PWD/../shared + +OTHER_FILES += \ + $$PWD/qqmldebuggerservice.json + diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json new file mode 100644 index 0000000000..967a725903 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "DebugMessages", "QmlDebugger", "V8Debugger", "NativeQmlDebugger" ] +} diff --git a/src/plugins/qmltooling/qmldbg_qtquick2/qtquick2plugin.cpp b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.cpp index 88801ec9db..f379352cfa 100644 --- a/src/plugins/qmltooling/qmldbg_qtquick2/qtquick2plugin.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.cpp @@ -31,48 +31,30 @@ ** ****************************************************************************/ -#include "qtquick2plugin.h" -#include "qquickviewinspector.h" +#include "qqmldebuggerservicefactory.h" +#include "qqmlenginedebugservice.h" +#include "qdebugmessageservice.h" +#include "qv4debugservice.h" +#include "qqmlnativedebugservice.h" +#include <private/qqmldebugserviceinterfaces_p.h> -#include <QtCore/qplugin.h> -#include <QtQml/private/qqmlinspectorservice_p.h> -#include <QtQuick/QQuickView> +QT_BEGIN_NAMESPACE -namespace QmlJSDebugger { -namespace QtQuick2 { - -QtQuick2Plugin::QtQuick2Plugin() : - m_inspector(0) -{ -} - -QtQuick2Plugin::~QtQuick2Plugin() +QQmlDebugService *QQmlDebuggerServiceFactory::create(const QString &key) { - delete m_inspector; -} + if (key == QDebugMessageService::s_key) + return new QDebugMessageService(this); -bool QtQuick2Plugin::canHandleView(QObject *view) -{ - return qobject_cast<QQuickView*>(view); -} + if (key == QQmlEngineDebugServiceImpl::s_key) + return new QQmlEngineDebugServiceImpl(this); -void QtQuick2Plugin::activate(QObject *view) -{ - QQuickView *qtQuickView = qobject_cast<QQuickView*>(view); - Q_ASSERT(qtQuickView); - m_inspector = new QQuickViewInspector(qtQuickView, qtQuickView); -} + if (key == QV4DebugServiceImpl::s_key) + return new QV4DebugServiceImpl(this); -void QtQuick2Plugin::deactivate() -{ - delete m_inspector; -} + if (key == QQmlNativeDebugServiceImpl::s_key) + return new QQmlNativeDebugServiceImpl(this); -void QtQuick2Plugin::clientMessage(const QByteArray &message) -{ - if (m_inspector) - m_inspector->handleMessage(message); + return 0; } -} // namespace QtQuick2 -} // namespace QmlJSDebugger +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.h b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.h new file mode 100644 index 0000000000..2c7509ba12 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLDEBUGGERSERVICEFACTORY_H +#define QQMLDEBUGGERSERVICEFACTORY_H + +#include <private/qqmldebugservicefactory_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlDebuggerServiceFactory : public QQmlDebugServiceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlDebugServiceFactory_iid FILE "qqmldebuggerservice.json") +public: + QQmlDebugService *create(const QString &key); +}; + +QT_END_NAMESPACE + +#endif // QQMLDEBUGGERSERVICEFACTORY_H diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp b/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp new file mode 100644 index 0000000000..8f53dc6d50 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp @@ -0,0 +1,820 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlenginedebugservice.h" +#include "qqmlwatcher.h" + +#include <private/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/qqmlvaluetype_p.h> +#include <private/qqmlvmemetaobject_p.h> +#include <private/qqmlexpression_p.h> + +#include <QtCore/qdebug.h> +#include <QtCore/qmetaobject.h> +#include <QtCore/qfileinfo.h> +#include <private/qmetaobject_p.h> + +QT_BEGIN_NAMESPACE + +QQmlEngineDebugServiceImpl::QQmlEngineDebugServiceImpl(QObject *parent) : + QQmlEngineDebugService(2, 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))); +} + +QQmlEngineDebugServiceImpl::~QQmlEngineDebugServiceImpl() +{ + delete m_statesDelegate; +} + +QDataStream &operator<<(QDataStream &ds, + const QQmlEngineDebugServiceImpl::QQmlObjectData &data) +{ + ds << data.url << data.lineNumber << data.columnNumber << data.idString + << data.objectName << data.objectType << data.objectId << data.contextId + << data.parentId; + return ds; +} + +QDataStream &operator>>(QDataStream &ds, + QQmlEngineDebugServiceImpl::QQmlObjectData &data) +{ + ds >> data.url >> data.lineNumber >> data.columnNumber >> data.idString + >> data.objectName >> data.objectType >> data.objectId >> data.contextId + >> data.parentId; + return ds; +} + +QDataStream &operator<<(QDataStream &ds, + const QQmlEngineDebugServiceImpl::QQmlObjectProperty &data) +{ + ds << (int)data.type << data.name; + // check first whether the data can be saved + // (otherwise we assert in QVariant::operator<<) + QByteArray buffer; + QDataStream fakeStream(&buffer, QIODevice::WriteOnly); + if (QMetaType::save(fakeStream, data.value.type(), data.value.constData())) + ds << data.value; + else + ds << QVariant(); + ds << data.valueTypeName << data.binding << data.hasNotifySignal; + return ds; +} + +QDataStream &operator>>(QDataStream &ds, + QQmlEngineDebugServiceImpl::QQmlObjectProperty &data) +{ + int type; + ds >> type >> data.name >> data.value >> data.valueTypeName + >> data.binding >> data.hasNotifySignal; + data.type = (QQmlEngineDebugServiceImpl::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; +} + +QQmlEngineDebugServiceImpl::QQmlObjectProperty +QQmlEngineDebugServiceImpl::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; + } else if (prop.userType() == QMetaType::QVariant) { + rv.type = QQmlObjectProperty::Variant; + } + + QVariant value; + if (rv.type != QQmlObjectProperty::Unknown && prop.userType() != 0) { + value = prop.read(obj); + } + rv.value = valueContents(value); + + return rv; +} + +QVariant QQmlEngineDebugServiceImpl::valueContents(QVariant value) const +{ + // We can't send JS objects across the wire, so transform them to variant + // maps for serialization. + if (value.userType() == qMetaTypeId<QJSValue>()) + value = value.value<QJSValue>().toVariant(); + 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(); + contents.reserve(count); + 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)) { + const QMetaObject *mo = QQmlValueTypeFactory::metaObjectForMetaType(userType); + if (mo) { + int toStringIndex = mo->indexOfMethod("toString"); + if (toStringIndex != -1) { + QMetaMethod mm = mo->method(toStringIndex); + QMetaType info(userType); + QString s; + if (info.flags() & QMetaType::IsGadget + && mm.invokeOnGadget(value.data(), Q_RETURN_ARG(QString, s))) + return s; + } + } + + return value; + } + + if (QQmlMetaType::isQObject(userType)) { + QObject *o = QQmlMetaType::toQObject(value); + if (o) { + QString name = o->objectName(); + if (name.isEmpty()) + name = QStringLiteral("<unnamed object>"); + return name; + } + } + + return QString(QStringLiteral("<unknown value>")); +} + +void QQmlEngineDebugServiceImpl::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])) + --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; + 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; + } + + QQmlData *ddata = QQmlData::get(object); + if (ddata && ddata->signalHandlers) { + QQmlBoundSignal *signalHandler = ddata->signalHandlers; + + while (signalHandler) { + QQmlObjectProperty prop; + prop.type = QQmlObjectProperty::SignalProperty; + prop.hasNotifySignal = false; + QQmlBoundSignalExpression *expr = signalHandler->expression(); + if (expr) { + prop.value = expr->expression(); + QObject *scope = expr->scopeObject(); + if (scope) { + QString methodName = QString::fromLatin1(QMetaObjectPrivate::signal(scope->metaObject(), signalHandler->signalIndex()).name()); + if (!methodName.isEmpty()) { + prop.name = QLatin1String("on") + methodName[0].toUpper() + + methodName.mid(1); + } + } + } + fakeProperties << prop; + + signalHandler = nextSignal(signalHandler); + } + } + + 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 QQmlEngineDebugServiceImpl::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 QQmlEngineDebugServiceImpl::storeObjectIds(QObject *co) +{ + QQmlDebugService::idForObject(co); + QObjectList children = co->children(); + for (int ii = 0; ii < children.count(); ++ii) + storeObjectIds(children.at(ii)); +} + +void QQmlEngineDebugServiceImpl::buildObjectList(QDataStream &message, + QQmlContext *ctxt, + const QList<QPointer<QObject> > &instances) +{ + QQmlContextData *p = QQmlContextData::get(ctxt); + + QString ctxtName = ctxt->objectName(); + int ctxtId = QQmlDebugService::idForObject(ctxt); + if (ctxt->contextObject()) + storeObjectIds(ctxt->contextObject()); + + 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(), instances); + child = child->nextChild; + } + + count = 0; + for (int ii = 0; ii < instances.count(); ++ii) { + QQmlData *data = QQmlData::get(instances.at(ii)); + if (data->context == p) + count ++; + } + message << count; + + for (int ii = 0; ii < instances.count(); ++ii) { + QQmlData *data = QQmlData::get(instances.at(ii)); + if (data->context == p) + message << objectData(instances.at(ii)); + } +} + +void QQmlEngineDebugServiceImpl::buildStatesList(bool cleanList, + const QList<QPointer<QObject> > &instances) +{ + if (m_statesDelegate) + m_statesDelegate->buildStatesList(cleanList, instances); +} + +QQmlEngineDebugServiceImpl::QQmlObjectData +QQmlEngineDebugServiceImpl::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)); + rv.parentId = QQmlDebugService::idForObject(object->parent()); + 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 QQmlEngineDebugServiceImpl::messageReceived(const QByteArray &message) +{ + QMetaObject::invokeMethod(this, "processMessage", Qt::QueuedConnection, Q_ARG(QByteArray, message)); +} + +/*! + Returns a list of objects matching the given filename, line and column. +*/ +QList<QObject*> QQmlEngineDebugServiceImpl::objectForLocationInfo(const QString &filename, + int lineNumber, int columnNumber) +{ + QList<QObject *> objects; + const QHash<int, QObject *> &hash = objectsForIds(); + for (QHash<int, QObject *>::ConstIterator i = hash.constBegin(); i != hash.constEnd(); ++i) { + QQmlData *ddata = QQmlData::get(i.value()); + if (ddata && ddata->outerContext) { + if (QFileInfo(ddata->outerContext->urlString()).fileName() == filename && + ddata->lineNumber == lineNumber && + ddata->columnNumber >= columnNumber) { + objects << i.value(); + } + } + } + return objects; +} + +void QQmlEngineDebugServiceImpl::processMessage(const QByteArray &message) +{ + QQmlDebugStream ds(message); + + QByteArray type; + int queryId; + ds >> type >> queryId; + + QByteArray reply; + QQmlDebugStream rs(&reply, QIODevice::WriteOnly); + + if (type == "LIST_ENGINES") { + 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; + } + + } else if (type == "LIST_OBJECTS") { + int engineId = -1; + ds >> engineId; + + QQmlEngine *engine = + qobject_cast<QQmlEngine *>(QQmlDebugService::objectForId(engineId)); + + rs << QByteArray("LIST_OBJECTS_R") << queryId; + + if (engine) { + QQmlContext *rootContext = engine->rootContext(); + // Clean deleted objects + QQmlContextPrivate *ctxtPriv = QQmlContextPrivate::get(rootContext); + for (int ii = 0; ii < ctxtPriv->instances.count(); ++ii) { + if (!ctxtPriv->instances.at(ii)) { + ctxtPriv->instances.removeAt(ii); + --ii; + } + } + buildObjectList(rs, rootContext, ctxtPriv->instances); + buildStatesList(true, ctxtPriv->instances); + } + + } else if (type == "FETCH_OBJECT") { + int objectId; + bool recurse; + bool dumpProperties = true; + + ds >> objectId >> recurse >> dumpProperties; + + QObject *object = QQmlDebugService::objectForId(objectId); + + rs << QByteArray("FETCH_OBJECT_R") << queryId; + + if (object) { + if (recurse) + prepareDeferredObjects(object); + buildObjectDump(rs, object, recurse, dumpProperties); + } + + } else if (type == "FETCH_OBJECTS_FOR_LOCATION") { + QString file; + int lineNumber; + int columnNumber; + bool recurse; + bool dumpProperties = true; + + ds >> file >> lineNumber >> columnNumber >> recurse >> dumpProperties; + + QList<QObject*> objects = objectForLocationInfo(file, lineNumber, columnNumber); + + rs << QByteArray("FETCH_OBJECTS_FOR_LOCATION_R") << queryId + << objects.count(); + + foreach (QObject *object, objects) { + if (recurse) + prepareDeferredObjects(object); + buildObjectDump(rs, object, recurse, dumpProperties); + } + + } else if (type == "WATCH_OBJECT") { + int objectId; + + ds >> objectId; + bool ok = m_watch->addWatch(queryId, objectId); + + rs << QByteArray("WATCH_OBJECT_R") << queryId << ok; + + } else if (type == "WATCH_PROPERTY") { + int objectId; + QByteArray property; + + ds >> objectId >> property; + bool ok = m_watch->addWatch(queryId, objectId, property); + + rs << QByteArray("WATCH_PROPERTY_R") << queryId << ok; + + } else if (type == "WATCH_EXPR_OBJECT") { + int debugId; + QString expr; + + ds >> debugId >> expr; + bool ok = m_watch->addWatch(queryId, debugId, expr); + + rs << QByteArray("WATCH_EXPR_OBJECT_R") << queryId << ok; + + } else if (type == "NO_WATCH") { + bool ok = m_watch->removeWatch(queryId); + + rs << QByteArray("NO_WATCH_R") << queryId << ok; + + } else if (type == "EVAL_EXPRESSION") { + int objectId; + QString expr; + + ds >> objectId >> expr; + int engineId = -1; + if (!ds.atEnd()) + ds >> engineId; + + QObject *object = QQmlDebugService::objectForId(objectId); + QQmlContext *context = qmlContext(object); + if (!context) { + QQmlEngine *engine = qobject_cast<QQmlEngine *>( + QQmlDebugService::objectForId(engineId)); + if (engine && m_engines.contains(engine)) + context = engine->rootContext(); + } + QVariant result; + if (context) { + QQmlExpression exprObj(context, object, expr); + bool undefined = false; + QVariant value = exprObj.evaluate(&undefined); + if (undefined) + result = QString(QStringLiteral("<undefined>")); + else + result = valueContents(value); + } else { + result = QString(QStringLiteral("<unknown context>")); + } + + rs << QByteArray("EVAL_EXPRESSION_R") << queryId << result; + + } else if (type == "SET_BINDING") { + int objectId; + QString propertyName; + QVariant expr; + bool isLiteralValue; + QString filename; + int line; + ds >> objectId >> propertyName >> expr >> isLiteralValue >> + filename >> line; + bool ok = setBinding(objectId, propertyName, expr, isLiteralValue, + filename, line); + + rs << QByteArray("SET_BINDING_R") << queryId << ok; + + } else if (type == "RESET_BINDING") { + int objectId; + QString propertyName; + ds >> objectId >> propertyName; + bool ok = resetBinding(objectId, propertyName); + + rs << QByteArray("RESET_BINDING_R") << queryId << ok; + + } else if (type == "SET_METHOD_BODY") { + int objectId; + QString methodName; + QString methodBody; + ds >> objectId >> methodName >> methodBody; + bool ok = setMethodBody(objectId, methodName, methodBody); + + rs << QByteArray("SET_METHOD_BODY_R") << queryId << ok; + + } + emit messageToClient(name(), reply); +} + +bool QQmlEngineDebugServiceImpl::setBinding(int objectId, + const QString &propertyName, + const QVariant &expression, + bool isLiteralValue, + QString filename, + int line, + int column) +{ + bool ok = true; + 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)) { + QQmlBoundSignalExpression *qmlExpression = new QQmlBoundSignalExpression(object, QQmlPropertyPrivate::get(property)->signalIndex(), + QQmlContextData::get(context), object, expression.toString(), + filename, line, column); + QQmlPropertyPrivate::takeSignalExpression(property, qmlExpression); + } else if (property.isProperty()) { + QQmlBinding *binding = new QQmlBinding(expression.toString(), object, QQmlContextData::get(context), filename, line, column); + binding->setTarget(property); + QQmlPropertyPrivate::setBinding(binding); + binding->update(); + } else { + ok = false; + qWarning() << "QQmlEngineDebugService::setBinding: unable to set property" << propertyName << "on object" << object; + } + } + + } else { + // not a valid property + if (m_statesDelegate) + ok = m_statesDelegate->setBindingForInvalidProperty(object, propertyName, expression, isLiteralValue); + if (!ok) + qWarning() << "QQmlEngineDebugService::setBinding: unable to set property" << propertyName << "on object" << object; + } + } + return ok; +} + +bool QQmlEngineDebugServiceImpl::resetBinding(int objectId, const QString &propertyName) +{ + QObject *object = objectForId(objectId); + QQmlContext *context = qmlContext(object); + + if (object && context) { + QString parentProperty = propertyName; + if (propertyName.indexOf(QLatin1Char('.')) != -1) + parentProperty = propertyName.left(propertyName.indexOf(QLatin1Char('.'))); + + if (object->property(parentProperty.toLatin1()).isValid()) { + QQmlProperty property(object, propertyName); + QQmlPropertyPrivate::removeBinding(property); + 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(parentProperty.toLatin1()).isValid()) { + QVariant defaultValue = QQmlProperty(emptyObject, propertyName).read(); + if (defaultValue.isValid()) { + setBinding(objectId, propertyName, defaultValue, true); + } + } + delete emptyObject; + } + } + } + return true; + } + + if (hasValidSignal(object, propertyName)) { + QQmlProperty property(object, propertyName, context); + QQmlPropertyPrivate::setSignalExpression(property, 0); + return true; + } + + if (m_statesDelegate) { + m_statesDelegate->resetBindingForInvalidProperty(object, propertyName); + return true; + } + + return false; + } + // object or context null. + return false; +} + +bool QQmlEngineDebugServiceImpl::setMethodBody(int objectId, const QString &method, const QString &body) +{ + QObject *object = objectForId(objectId); + QQmlContext *context = qmlContext(object); + if (!object || !context || !context->engine()) + return false; + QQmlContextData *contextData = QQmlContextData::get(context); + if (!contextData) + return false; + + QQmlPropertyData dummy; + QQmlPropertyData *prop = + QQmlPropertyCache::property(context->engine(), object, method, contextData, dummy); + + if (!prop || !prop->isVMEFunction()) + return false; + + 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(QLatin1Char(',')); + paramStr.append(QString::fromUtf8(paramNames.at(ii))); + } + + QString jsfunction = QLatin1String("(function ") + method + QLatin1Char('(') + paramStr + + QLatin1String(") {"); + jsfunction += body; + jsfunction += QLatin1String("\n})"); + + QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(object); + Q_ASSERT(vmeMetaObject); // the fact we found the property above should guarentee this + + int lineNumber = vmeMetaObject->vmeMethodLineNumber(prop->coreIndex); + QV4::ExecutionEngine *v4 = QV8Engine::getV4(qmlEngine(object)->handle()); + QV4::Scope scope(v4); + QV4::ScopedValue v(scope, QQmlJavaScriptExpression::evalFunction(contextData, object, jsfunction, contextData->urlString(), lineNumber)); + vmeMetaObject->setVmeMethod(prop->coreIndex, v); + return true; +} + +void QQmlEngineDebugServiceImpl::propertyChanged(int id, int objectId, const QMetaProperty &property, const QVariant &value) +{ + QByteArray reply; + QQmlDebugStream rs(&reply, QIODevice::WriteOnly); + + rs << QByteArray("UPDATE_WATCH") << id << objectId << QByteArray(property.name()) << valueContents(value); + + emit messageToClient(name(), reply); +} + +void QQmlEngineDebugServiceImpl::engineAboutToBeAdded(QQmlEngine *engine) +{ + Q_ASSERT(engine); + Q_ASSERT(!m_engines.contains(engine)); + + m_engines.append(engine); + emit attachedToEngine(engine); +} + +void QQmlEngineDebugServiceImpl::engineAboutToBeRemoved(QQmlEngine *engine) +{ + Q_ASSERT(engine); + Q_ASSERT(m_engines.contains(engine)); + + m_engines.removeAll(engine); + emit detachedFromEngine(engine); +} + +void QQmlEngineDebugServiceImpl::objectCreated(QQmlEngine *engine, QObject *object) +{ + Q_ASSERT(engine); + Q_ASSERT(m_engines.contains(engine)); + + int engineId = QQmlDebugService::idForObject(engine); + int objectId = QQmlDebugService::idForObject(object); + int parentId = QQmlDebugService::idForObject(object->parent()); + + QByteArray reply; + QQmlDebugStream rs(&reply, QIODevice::WriteOnly); + + //unique queryId -1 + rs << QByteArray("OBJECT_CREATED") << -1 << engineId << objectId << parentId; + emit messageToClient(name(), reply); +} + +void QQmlEngineDebugServiceImpl::setStatesDelegate(QQmlDebugStatesDelegate *delegate) +{ + m_statesDelegate = delegate; +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.h b/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.h new file mode 100644 index 0000000000..68cb420cc0 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.h @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLENGINEDEBUGSERVICE_H +#define QQMLENGINEDEBUGSERVICE_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 <private/qqmldebugserviceinterfaces_p.h> + +#include <QtCore/qurl.h> +#include <QtCore/qvariant.h> +#include <QtCore/QPointer> + +QT_BEGIN_NAMESPACE + +class QQmlEngine; +class QQmlContext; +class QQmlWatcher; +class QDataStream; +class QQmlDebugStatesDelegate; + +class QQmlEngineDebugServiceImpl : public QQmlEngineDebugService +{ + Q_OBJECT +public: + QQmlEngineDebugServiceImpl(QObject * = 0); + ~QQmlEngineDebugServiceImpl(); + + struct QQmlObjectData { + QUrl url; + int lineNumber; + int columnNumber; + QString idString; + QString objectName; + QString objectType; + int objectId; + int contextId; + int parentId; + }; + + struct QQmlObjectProperty { + enum Type { Unknown, Basic, Object, List, SignalProperty, Variant }; + Type type; + QString name; + QVariant value; + QString valueTypeName; + QString binding; + bool hasNotifySignal; + }; + + void engineAboutToBeAdded(QQmlEngine *) Q_DECL_OVERRIDE; + void engineAboutToBeRemoved(QQmlEngine *) Q_DECL_OVERRIDE; + void objectCreated(QQmlEngine *, QObject *) Q_DECL_OVERRIDE; + + void setStatesDelegate(QQmlDebugStatesDelegate *) Q_DECL_OVERRIDE; + +protected: + virtual void messageReceived(const QByteArray &) Q_DECL_OVERRIDE; + +private Q_SLOTS: + void processMessage(const QByteArray &msg); + void propertyChanged(int id, int objectId, const QMetaProperty &property, const QVariant &value); + +private: + friend class QQmlDebuggerServiceFactory; + + void prepareDeferredObjects(QObject *); + void buildObjectList(QDataStream &, QQmlContext *, + const QList<QPointer<QObject> > &instances); + void buildObjectDump(QDataStream &, QObject *, bool, bool); + void buildStatesList(bool cleanList, const QList<QPointer<QObject> > &instances); + QQmlObjectData objectData(QObject *); + QQmlObjectProperty propertyData(QObject *, int); + QVariant valueContents(QVariant defaultValue) const; + bool setBinding(int objectId, const QString &propertyName, const QVariant &expression, bool isLiteralValue, QString filename = QString(), int line = -1, int column = 0); + bool resetBinding(int objectId, const QString &propertyName); + bool setMethodBody(int objectId, const QString &method, const QString &body); + void storeObjectIds(QObject *co); + QList<QObject *> objectForLocationInfo(const QString &filename, int lineNumber, + int columnNumber); + + QList<QQmlEngine *> m_engines; + QQmlWatcher *m_watch; + QQmlDebugStatesDelegate *m_statesDelegate; +}; +QDataStream &operator<<(QDataStream &, const QQmlEngineDebugServiceImpl::QQmlObjectData &); +QDataStream &operator>>(QDataStream &, QQmlEngineDebugServiceImpl::QQmlObjectData &); +QDataStream &operator<<(QDataStream &, const QQmlEngineDebugServiceImpl::QQmlObjectProperty &); +QDataStream &operator>>(QDataStream &, QQmlEngineDebugServiceImpl::QQmlObjectProperty &); + +QT_END_NAMESPACE + +#endif // QQMLENGINEDEBUGSERVICE_H + diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.cpp b/src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.cpp new file mode 100644 index 0000000000..f5cc78e77f --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.cpp @@ -0,0 +1,804 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlnativedebugservice.h" + +#include <private/qqmldebugconnector_p.h> +#include <private/qv4debugging_p.h> +#include <private/qv8engine_p.h> +#include <private/qv4engine_p.h> +#include <private/qv4debugging_p.h> +#include <private/qv4script_p.h> +#include <private/qv4string_p.h> +#include <private/qv4objectiterator_p.h> +#include <private/qv4identifier_p.h> +#include <private/qv4runtime_p.h> +#include <private/qv4isel_moth_p.h> +#include <private/qqmldebugserviceinterfaces_p.h> + +#include <qqmlengine.h> + +#include <QtCore/qjsonarray.h> +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qjsonvalue.h> +#include <QtCore/qvector.h> +#include <QtCore/qpointer.h> + +//#define TRACE_PROTOCOL(s) qDebug() << s +#define TRACE_PROTOCOL(s) + +QT_BEGIN_NAMESPACE + +class BreakPoint +{ +public: + BreakPoint() : id(-1), lineNumber(-1), enabled(false), ignoreCount(0), hitCount(0) {} + bool isValid() const { return lineNumber >= 0 && !fileName.isEmpty(); } + + int id; + int lineNumber; + QString fileName; + bool enabled; + QString condition; + int ignoreCount; + + int hitCount; +}; + +inline uint qHash(const BreakPoint &b, uint seed = 0) Q_DECL_NOTHROW +{ + return qHash(b.fileName, seed) ^ b.lineNumber; +} + +inline bool operator==(const BreakPoint &a, const BreakPoint &b) +{ + return a.lineNumber == b.lineNumber && a.fileName == b.fileName + && a.enabled == b.enabled && a.condition == b.condition + && a.ignoreCount == b.ignoreCount; +} + +static void setError(QJsonObject *response, const QString &msg) +{ + response->insert(QStringLiteral("type"), QStringLiteral("error")); + response->insert(QStringLiteral("msg"), msg); +} + +class NativeDebugger; + +class Collector +{ +public: + Collector(QV4::ExecutionEngine *engine) + : m_engine(engine), m_anonCount(0) + {} + + void collect(QJsonArray *output, const QString &parentIName, const QString &name, + const QV4::Value &value); + + bool isExpanded(const QString &iname) const { return m_expanded.contains(iname); } + +public: + QV4::ExecutionEngine *m_engine; + int m_anonCount; + QStringList m_expanded; +}; + +// Encapsulate Breakpoint handling +// Could be made per-NativeDebugger (i.e. per execution engine, if needed) +class BreakPointHandler +{ +public: + BreakPointHandler() : m_haveBreakPoints(false), m_breakOnThrow(true), m_lastBreakpoint(1) {} + + void handleSetBreakpoint(QJsonObject *response, const QJsonObject &arguments); + void handleRemoveBreakpoint(QJsonObject *response, const QJsonObject &arguments); + + void removeBreakPoint(int id); + void enableBreakPoint(int id, bool onoff); + + void setBreakOnThrow(bool onoff); + bool m_haveBreakPoints; + bool m_breakOnThrow; + int m_lastBreakpoint; + QVector<BreakPoint> m_breakPoints; +}; + +void BreakPointHandler::handleSetBreakpoint(QJsonObject *response, const QJsonObject &arguments) +{ + TRACE_PROTOCOL("SET BREAKPOINT" << arguments); + QString type = arguments.value(QStringLiteral("type")).toString(); + + QString fileName = arguments.value(QStringLiteral("file")).toString(); + if (fileName.isEmpty()) { + setError(response, QStringLiteral("breakpoint has no file name")); + return; + } + + int line = arguments.value(QStringLiteral("line")).toInt(-1); + if (line < 0) { + setError(response, QStringLiteral("breakpoint has an invalid line number")); + return; + } + + BreakPoint bp; + bp.id = m_lastBreakpoint++; + bp.fileName = fileName.mid(fileName.lastIndexOf('/') + 1); + bp.lineNumber = line; + bp.enabled = arguments.value(QStringLiteral("enabled")).toBool(true); + bp.condition = arguments.value(QStringLiteral("condition")).toString(); + bp.ignoreCount = arguments.value(QStringLiteral("ignorecount")).toInt(); + m_breakPoints.append(bp); + + m_haveBreakPoints = true; + + response->insert(QStringLiteral("type"), type); + response->insert(QStringLiteral("breakpoint"), bp.id); +} + +void BreakPointHandler::handleRemoveBreakpoint(QJsonObject *response, const QJsonObject &arguments) +{ + int id = arguments.value(QStringLiteral("id")).toInt(); + removeBreakPoint(id); + response->insert(QStringLiteral("id"), id); +} + +class NativeDebugger : public QV4::Debugging::Debugger +{ +public: + NativeDebugger(QQmlNativeDebugServiceImpl *service, QV4::ExecutionEngine *engine); + + void signalEmitted(const QString &signal); + + QV4::ExecutionEngine *engine() const { return m_engine; } + + bool pauseAtNextOpportunity() const Q_DECL_OVERRIDE { + return m_pauseRequested + || m_service->m_breakHandler->m_haveBreakPoints + || m_stepping >= StepOver; + } + + void maybeBreakAtInstruction() Q_DECL_OVERRIDE; + void enteringFunction() Q_DECL_OVERRIDE; + void leavingFunction(const QV4::ReturnedValue &retVal) Q_DECL_OVERRIDE; + void aboutToThrow() Q_DECL_OVERRIDE; + + void handleCommand(QJsonObject *response, const QString &cmd, const QJsonObject &arguments); + +private: + void handleBacktrace(QJsonObject *response, const QJsonObject &arguments); + void handleVariables(QJsonObject *response, const QJsonObject &arguments); + void handleExpressions(QJsonObject *response, const QJsonObject &arguments); + + void handleDebuggerDeleted(QObject *debugger); + + QV4::ReturnedValue evaluateExpression(QV4::Scope &scope, const QString &expression); + bool checkCondition(const QString &expression); + + QStringList breakOnSignals; + + enum Speed { + NotStepping = 0, + StepOut, + StepOver, + StepIn, + }; + + void pauseAndWait(); + void pause(); + void handleContinue(QJsonObject *reponse, Speed speed); + + QV4::Function *getFunction() const; + + bool reallyHitTheBreakPoint(const QV4::Function *function, int lineNumber); + + QV4::ExecutionEngine *m_engine; + QQmlNativeDebugServiceImpl *m_service; + QV4::PersistentValue m_currentContext; + Speed m_stepping; + bool m_pauseRequested; + bool m_runningJob; + + QV4::PersistentValue m_returnedValue; +}; + +bool NativeDebugger::checkCondition(const QString &expression) +{ + QV4::Scope scope(m_engine); + QV4::ReturnedValue result = evaluateExpression(scope, expression); + QV4::ScopedValue val(scope, result); + return val->booleanValue(); +} + +QV4::ReturnedValue NativeDebugger::evaluateExpression(QV4::Scope &scope, const QString &expression) +{ + m_runningJob = true; + + QV4::ExecutionContextSaver saver(scope); + + QV4::ExecutionContext *ctx = m_engine->currentContext; + m_engine->pushContext(ctx); + + QV4::Script script(ctx, expression); + script.strictMode = ctx->d()->strictMode; + // In order for property lookups in QML to work, we need to disable fast v4 lookups. + // That is a side-effect of inheritContext. + script.inheritContext = true; + script.parse(); + QV4::ScopedValue result(scope); + if (!m_engine->hasException) + result = script.run(); + + m_runningJob = false; + return result->asReturnedValue(); +} + +NativeDebugger::NativeDebugger(QQmlNativeDebugServiceImpl *service, QV4::ExecutionEngine *engine) + : m_returnedValue(engine, QV4::Primitive::undefinedValue()) +{ + m_stepping = NotStepping; + m_pauseRequested = false; + m_runningJob = false; + m_service = service; + m_engine = engine; + TRACE_PROTOCOL("Creating native debugger"); +} + +void NativeDebugger::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. + + //Parse just the name and remove the class info + //Normalize to Lower case. + QString signalName = signal.left(signal.indexOf(QLatin1Char('('))).toLower(); + + foreach (const QString &signal, breakOnSignals) { + if (signal == signalName) { + // TODO: pause debugger + break; + } + } +} + +void NativeDebugger::handleCommand(QJsonObject *response, const QString &cmd, + const QJsonObject &arguments) +{ + if (cmd == QStringLiteral("backtrace")) + handleBacktrace(response, arguments); + else if (cmd == QStringLiteral("variables")) + handleVariables(response, arguments); + else if (cmd == QStringLiteral("expressions")) + handleExpressions(response, arguments); + else if (cmd == QStringLiteral("stepin")) + handleContinue(response, StepIn); + else if (cmd == QStringLiteral("stepout")) + handleContinue(response, StepOut); + else if (cmd == QStringLiteral("stepover")) + handleContinue(response, StepOver); + else if (cmd == QStringLiteral("continue")) + handleContinue(response, NotStepping); +} + +static QString encodeContext(QV4::ExecutionContext *executionContext) +{ + QByteArray ba; + QDataStream ds(&ba, QIODevice::WriteOnly); + ds << quintptr(executionContext); + return QString::fromLatin1(ba.toHex()); +} + +static void decodeContext(const QString &context, QV4::ExecutionContext **executionContext) +{ + quintptr rawContext; + QDataStream ds(QByteArray::fromHex(context.toLatin1())); + ds >> rawContext; + *executionContext = reinterpret_cast<QV4::ExecutionContext *>(rawContext); +} + +void NativeDebugger::handleBacktrace(QJsonObject *response, const QJsonObject &arguments) +{ + int limit = arguments.value(QStringLiteral("limit")).toInt(0); + + QJsonArray frameArray; + QV4::ExecutionContext *executionContext = m_engine->currentContext; + for (int i = 0; i < limit && executionContext; ++i) { + QV4::Heap::FunctionObject *heapFunctionObject = executionContext->getFunctionObject(); + if (heapFunctionObject) { + + QJsonObject frame; + frame[QStringLiteral("language")] = QStringLiteral("js"); + frame[QStringLiteral("context")] = encodeContext(executionContext); + + if (QV4::Function *function = heapFunctionObject->function) { + if (QV4::Heap::String *functionName = function->name()) + frame[QStringLiteral("function")] = functionName->toQString(); + frame[QStringLiteral("file")] = function->sourceFile(); + } + int line = executionContext->d()->lineNumber; + frame[QStringLiteral("line")] = (line < 0 ? -line : line); + + frameArray.push_back(frame); + } + + executionContext = m_engine->parentContext(executionContext); + } + + response->insert(QStringLiteral("frames"), frameArray); +} + +void Collector::collect(QJsonArray *out, const QString &parentIName, const QString &name, + const QV4::Value &value) +{ + QJsonObject dict; + QV4::Scope scope(m_engine); + + QString nonEmptyName = name.isEmpty() ? QString::fromLatin1("@%1").arg(m_anonCount++) : name; + QString iname = parentIName + QLatin1Char('.') + nonEmptyName; + dict.insert(QStringLiteral("iname"), iname); + dict.insert(QStringLiteral("name"), nonEmptyName); + + QV4::ScopedValue typeString(scope, QV4::Runtime::typeofValue(m_engine, value)); + dict.insert(QStringLiteral("type"), typeString->toQStringNoThrow()); + + switch (value.type()) { + case QV4::Value::Empty_Type: + dict.insert(QStringLiteral("valueencoded"), QStringLiteral("empty")); + dict.insert(QStringLiteral("haschild"), false); + break; + case QV4::Value::Undefined_Type: + dict.insert(QStringLiteral("valueencoded"), QStringLiteral("undefined")); + dict.insert(QStringLiteral("haschild"), false); + break; + case QV4::Value::Null_Type: + dict.insert(QStringLiteral("type"), QStringLiteral("object")); + dict.insert(QStringLiteral("valueencoded"), QStringLiteral("null")); + dict.insert(QStringLiteral("haschild"), false); + break; + case QV4::Value::Boolean_Type: + dict.insert(QStringLiteral("value"), value.booleanValue()); + dict.insert(QStringLiteral("haschild"), false); + break; + case QV4::Value::Managed_Type: + if (const QV4::String *string = value.as<QV4::String>()) { + dict.insert(QStringLiteral("value"), string->toQStringNoThrow()); + dict.insert(QStringLiteral("haschild"), false); + dict.insert(QStringLiteral("valueencoded"), QStringLiteral("utf16")); + dict.insert(QStringLiteral("quoted"), true); + } else if (const QV4::ArrayObject *array = value.as<QV4::ArrayObject>()) { + // The size of an array is number of its numerical properties. + // We don't consider free form object properties here. + const uint n = array->getLength(); + dict.insert(QStringLiteral("value"), qint64(n)); + dict.insert(QStringLiteral("valueencoded"), QStringLiteral("itemcount")); + dict.insert(QStringLiteral("haschild"), qint64(n)); + if (isExpanded(iname)) { + QJsonArray children; + for (uint i = 0; i < n; ++i) { + QV4::ReturnedValue v = array->getIndexed(i); + QV4::ScopedValue sval(scope, v); + collect(&children, iname, QString::number(i), *sval); + } + dict.insert(QStringLiteral("children"), children); + } + } else if (const QV4::Object *object = value.as<QV4::Object>()) { + QJsonArray children; + bool expanded = isExpanded(iname); + qint64 numProperties = 0; + QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly); + QV4::ScopedProperty p(scope); + QV4::ScopedString name(scope); + while (true) { + QV4::PropertyAttributes attrs; + uint index; + it.next(name.getRef(), &index, p, &attrs); + if (attrs.isEmpty()) + break; + if (name.getPointer()) { + ++numProperties; + if (expanded) { + if (name.getPointer()) { + QV4::Value v = p.property->value; + collect(&children, iname, name->toQStringNoThrow(), v); + } + } + } + } + dict.insert(QStringLiteral("value"), numProperties); + dict.insert(QStringLiteral("valueencoded"), QStringLiteral("itemcount")); + dict.insert(QStringLiteral("haschild"), numProperties > 0); + if (expanded) + dict.insert(QStringLiteral("children"), children); + } + break; + case QV4::Value::Integer_Type: + dict.insert(QStringLiteral("value"), value.integerValue()); + dict.insert(QStringLiteral("haschild"), false); + break; + default: // double + dict.insert(QStringLiteral("value"), value.doubleValue()); + dict.insert(QStringLiteral("haschild"), false); + } + + out->append(dict); +} + +void NativeDebugger::handleVariables(QJsonObject *response, const QJsonObject &arguments) +{ + TRACE_PROTOCOL("Build variables"); + QV4::ExecutionContext *executionContext = 0; + decodeContext(arguments.value(QStringLiteral("context")).toString(), &executionContext); + if (!executionContext) { + setError(response, QStringLiteral("No execution context passed")); + return; + } + TRACE_PROTOCOL("Context: " << executionContext); + + QV4::ExecutionEngine *engine = executionContext->d()->engine; + if (!engine) { + setError(response, QStringLiteral("No execution engine passed")); + return; + } + TRACE_PROTOCOL("Engine: " << engine); + + Collector collector(engine); + QJsonArray expanded = arguments.value(QStringLiteral("expanded")).toArray(); + foreach (const QJsonValue &ex, expanded) + collector.m_expanded.append(ex.toString()); + TRACE_PROTOCOL("Expanded: " << collector.m_expanded); + + QJsonArray output; + QV4::Scope scope(engine); + + if (QV4::CallContext *callContext = executionContext->asCallContext()) { + QV4::Value thisObject = callContext->thisObject(); + collector.collect(&output, QString(), QStringLiteral("this"), thisObject); + QV4::Identifier *const *variables = callContext->variables(); + QV4::Identifier *const *formals = callContext->formals(); + for (unsigned i = 0, ei = callContext->variableCount(); i != ei; ++i) { + QString qName; + if (QV4::Identifier *name = variables[i]) + qName = name->string; + QV4::Value val = callContext->d()->locals[i]; + collector.collect(&output, QString(), qName, val); + } + for (unsigned i = 0, ei = callContext->formalCount(); i != ei; ++i) { + QString qName; + if (QV4::Identifier *name = formals[i]) + qName = name->string; + QV4::ReturnedValue rval = callContext->argument(i); + QV4::ScopedValue sval(scope, rval); + collector.collect(&output, QString(), qName, *sval); + } + } + + response->insert(QStringLiteral("variables"), output); +} + +void NativeDebugger::handleExpressions(QJsonObject *response, const QJsonObject &arguments) +{ + TRACE_PROTOCOL("Evaluate expressions"); + QV4::ExecutionContext *executionContext = 0; + decodeContext(arguments.value(QStringLiteral("context")).toString(), &executionContext); + if (!executionContext) { + setError(response, QStringLiteral("No execution context passed")); + return; + } + TRACE_PROTOCOL("Context: " << executionContext); + + QV4::ExecutionEngine *engine = executionContext->d()->engine; + if (!engine) { + setError(response, QStringLiteral("No execution engine passed")); + return; + } + TRACE_PROTOCOL("Engines: " << engine << m_engine); + + Collector collector(engine); + QJsonArray expanded = arguments.value(QStringLiteral("expanded")).toArray(); + foreach (const QJsonValue &ex, expanded) + collector.m_expanded.append(ex.toString()); + TRACE_PROTOCOL("Expanded: " << collector.m_expanded); + + QJsonArray output; + QV4::Scope scope(engine); + + QJsonArray expressions = arguments.value(QStringLiteral("expressions")).toArray(); + foreach (const QJsonValue &expr, expressions) { + QString expression = expr.toObject().value(QStringLiteral("expression")).toString(); + QString name = expr.toObject().value(QStringLiteral("name")).toString(); + TRACE_PROTOCOL("Evaluate expression: " << expression); + m_runningJob = true; + + QV4::ReturnedValue eval = evaluateExpression(scope, expression); + QV4::ScopedValue result(scope, eval); + + m_runningJob = false; + if (result->isUndefined()) { + QJsonObject dict; + dict[QStringLiteral("name")] = name; + dict[QStringLiteral("valueencoded")] = QStringLiteral("undefined"); + output.append(dict); + } else if (result.ptr && result.ptr->_val) { + collector.collect(&output, QString(), name, *result); + } else { + QJsonObject dict; + dict[QStringLiteral("name")] = name; + dict[QStringLiteral("valueencoded")] = QStringLiteral("notaccessible"); + output.append(dict); + } + TRACE_PROTOCOL("EXCEPTION: " << engine->hasException); + engine->hasException = false; + } + + response->insert(QStringLiteral("expressions"), output); +} + +void BreakPointHandler::removeBreakPoint(int id) +{ + for (int i = 0; i != m_breakPoints.size(); ++i) { + if (m_breakPoints.at(i).id == id) { + m_breakPoints.remove(i); + m_haveBreakPoints = !m_breakPoints.isEmpty(); + return; + } + } +} + +void BreakPointHandler::enableBreakPoint(int id, bool enabled) +{ + m_breakPoints[id].enabled = enabled; +} + +void NativeDebugger::pause() +{ + m_pauseRequested = true; +} + +void NativeDebugger::handleContinue(QJsonObject *response, Speed speed) +{ + Q_UNUSED(response); + + if (!m_returnedValue.isUndefined()) + m_returnedValue.set(m_engine, QV4::Encode::undefined()); + + m_currentContext.set(m_engine, *m_engine->currentContext); + m_stepping = speed; +} + +void NativeDebugger::maybeBreakAtInstruction() +{ + if (m_runningJob) // do not re-enter when we're doing a job for the debugger. + return; + + if (m_stepping == StepOver) { + if (m_currentContext.asManaged()->d() == m_engine->current) + pauseAndWait(); + return; + } + + if (m_stepping == StepIn) { + pauseAndWait(); + return; + } + + if (m_pauseRequested) { // Serve debugging requests from the agent + m_pauseRequested = false; + pauseAndWait(); + return; + } + + if (m_service->m_breakHandler->m_haveBreakPoints) { + if (QV4::Function *function = getFunction()) { + const int lineNumber = m_engine->current->lineNumber; + if (reallyHitTheBreakPoint(function, lineNumber)) + pauseAndWait(); + } + } +} + +void NativeDebugger::enteringFunction() +{ + if (m_runningJob) + return; + + if (m_stepping == StepIn) { + m_currentContext.set(m_engine, *m_engine->currentContext); + } +} + +void NativeDebugger::leavingFunction(const QV4::ReturnedValue &retVal) +{ + if (m_runningJob) + return; + + if (m_stepping != NotStepping && m_currentContext.asManaged()->d() == m_engine->current) { + m_currentContext.set(m_engine, *m_engine->parentContext(m_engine->currentContext)); + m_stepping = StepOver; + m_returnedValue.set(m_engine, retVal); + } +} + +void NativeDebugger::aboutToThrow() +{ + if (!m_service->m_breakHandler->m_breakOnThrow) + return; + + if (m_runningJob) // do not re-enter when we're doing a job for the debugger. + return; + + QJsonObject event; + // TODO: complete this! + event.insert(QStringLiteral("event"), QStringLiteral("exception")); + m_service->emitAsynchronousMessageToClient(event); +} + +QV4::Function *NativeDebugger::getFunction() const +{ + QV4::Scope scope(m_engine); + QV4::ExecutionContext *context = m_engine->currentContext; + QV4::ScopedFunctionObject function(scope, context->getFunctionObject()); + if (function) + return function->function(); + else + return context->d()->engine->globalCode; +} + +void NativeDebugger::pauseAndWait() +{ + QJsonObject event; + + event.insert(QStringLiteral("event"), QStringLiteral("break")); + event.insert(QStringLiteral("language"), QStringLiteral("js")); + if (QV4::ExecutionContext *executionContext = m_engine->currentContext) { + QV4::Heap::FunctionObject *heapFunctionObject = executionContext->getFunctionObject(); + if (heapFunctionObject) { + if (QV4::Function *function = heapFunctionObject->function) + event.insert(QStringLiteral("file"), function->sourceFile()); + int line = executionContext->d()->lineNumber; + event.insert(QStringLiteral("line"), (line < 0 ? -line : line)); + } + } + + m_service->emitAsynchronousMessageToClient(event); +} + +bool NativeDebugger::reallyHitTheBreakPoint(const QV4::Function *function, int lineNumber) +{ + for (int i = 0, n = m_service->m_breakHandler->m_breakPoints.size(); i != n; ++i) { + const BreakPoint &bp = m_service->m_breakHandler->m_breakPoints.at(i); + if (bp.lineNumber == lineNumber) { + const QString fileName = function->sourceFile(); + const QString base = fileName.mid(fileName.lastIndexOf('/') + 1); + if (bp.fileName.endsWith(base)) { + if (bp.condition.isEmpty() || checkCondition(bp.condition)) { + BreakPoint &mbp = m_service->m_breakHandler->m_breakPoints[i]; + ++mbp.hitCount; + if (mbp.hitCount > mbp.ignoreCount) + return true; + } + } + } + } + return false; +} + +QQmlNativeDebugServiceImpl::QQmlNativeDebugServiceImpl(QObject *parent) + : QQmlNativeDebugService(1.0, parent) +{ + m_breakHandler = new BreakPointHandler; +} + +QQmlNativeDebugServiceImpl::~QQmlNativeDebugServiceImpl() +{ + delete m_breakHandler; +} + +void QQmlNativeDebugServiceImpl::engineAboutToBeAdded(QQmlEngine *engine) +{ + TRACE_PROTOCOL("Adding engine" << engine); + if (engine) { + QV4::ExecutionEngine *ee = QV8Engine::getV4(engine->handle()); + TRACE_PROTOCOL("Adding execution engine" << ee); + if (ee) { + NativeDebugger *debugger = new NativeDebugger(this, ee); + ee->iselFactory.reset(new QV4::Moth::ISelFactory); + if (state() == Enabled) + ee->setDebugger(debugger); + m_debuggers.append(QPointer<NativeDebugger>(debugger)); + } + } + QQmlDebugService::engineAboutToBeAdded(engine); +} + +void QQmlNativeDebugServiceImpl::engineAboutToBeRemoved(QQmlEngine *engine) +{ + TRACE_PROTOCOL("Removing engine" << engine); + if (engine) { + QV4::ExecutionEngine *executionEngine = QV8Engine::getV4(engine->handle()); + foreach (NativeDebugger *debugger, m_debuggers) { + if (debugger->engine() == executionEngine) + m_debuggers.removeAll(debugger); + } + } + QQmlDebugService::engineAboutToBeRemoved(engine); +} + +void QQmlNativeDebugServiceImpl::stateAboutToBeChanged(QQmlDebugService::State state) +{ + if (state == Enabled) { + foreach (NativeDebugger *debugger, m_debuggers) { + QV4::ExecutionEngine *engine = debugger->engine(); + if (!engine->debugger) + engine->setDebugger(debugger); + } + } + QQmlDebugService::stateAboutToBeChanged(state); +} + +void QQmlNativeDebugServiceImpl::messageReceived(const QByteArray &message) +{ + TRACE_PROTOCOL("Native message received: " << message); + QJsonObject request = QJsonDocument::fromJson(message).object(); + QJsonObject response; + QJsonObject arguments = request.value(QStringLiteral("arguments")).toObject(); + QString cmd = request.value(QStringLiteral("command")).toString(); + + if (cmd == QStringLiteral("setbreakpoint")) { + m_breakHandler->handleSetBreakpoint(&response, arguments); + } else if (cmd == QStringLiteral("removebreakpoint")) { + m_breakHandler->handleRemoveBreakpoint(&response, arguments); + } else if (cmd == QStringLiteral("echo")) { + response.insert(QStringLiteral("result"), arguments); + } else { + foreach (NativeDebugger *debugger, m_debuggers) + if (debugger) + debugger->handleCommand(&response, cmd, arguments); + } + QJsonDocument doc; + doc.setObject(response); + QByteArray ba = doc.toJson(QJsonDocument::Compact); + TRACE_PROTOCOL("Sending synchronous response:" << ba.constData() << endl); + emit messageToClient(s_key, ba); +} + +void QQmlNativeDebugServiceImpl::emitAsynchronousMessageToClient(const QJsonObject &message) +{ + QJsonDocument doc; + doc.setObject(message); + QByteArray ba = doc.toJson(QJsonDocument::Compact); + TRACE_PROTOCOL("Sending asynchronous message:" << ba.constData() << endl); + emit messageToClient(s_key, ba); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.h b/src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.h new file mode 100644 index 0000000000..9d0780a203 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQML_NATIVE_DEBUG_SERVICE_H +#define QQML_NATIVE_DEBUG_SERVICE_H + +#include <private/qqmldebugconnector_p.h> +#include <private/qv4debugging_p.h> +#include <private/qv8engine_p.h> +#include <private/qv4engine_p.h> +#include <private/qv4debugging_p.h> +#include <private/qv4script_p.h> +#include <private/qv4string_p.h> +#include <private/qv4objectiterator_p.h> +#include <private/qv4identifier_p.h> +#include <private/qv4runtime_p.h> +#include <private/qqmldebugserviceinterfaces_p.h> + +#include <QtCore/qjsonarray.h> + +#include <qqmlengine.h> + +#include <QJsonArray> +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonValue> +#include <QVector> +#include <QPointer> + +QT_BEGIN_NAMESPACE + +class NativeDebugger; +class BreakPointHandler; +class QQmlDebuggerServiceFactory; + +class QQmlNativeDebugServiceImpl : public QQmlNativeDebugService +{ +public: + QQmlNativeDebugServiceImpl(QObject *parent); + + ~QQmlNativeDebugServiceImpl(); + + void engineAboutToBeAdded(QQmlEngine *engine); + void engineAboutToBeRemoved(QQmlEngine *engine); + + void stateAboutToBeChanged(State state); + + void messageReceived(const QByteArray &message); + + void emitAsynchronousMessageToClient(const QJsonObject &message); + +private: + friend class QQmlDebuggerServiceFactory; + friend class NativeDebugger; + + QList<QPointer<NativeDebugger> > m_debuggers; + BreakPointHandler *m_breakHandler; +}; + +QT_END_NAMESPACE + +#endif // QQML_NATIVE_DEBUG_SERVICE_H diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.cpp b/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.cpp new file mode 100644 index 0000000000..9f9a6eb33b --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.cpp @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlwatcher.h" + +#include "qqmlexpression.h" +#include "qqmlcontext.h" +#include "qqml.h" + +#include <private/qqmldebugservice_p.h> +#include <private/qqmlproperty_p.h> +#include <private/qqmlvaluetype_p.h> + +#include <QtCore/qmetaobject.h> +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + + +class QQmlWatchProxy : public QObject +{ + Q_OBJECT +public: + QQmlWatchProxy(int id, + QObject *object, + int debugId, + const QMetaProperty &prop, + QQmlWatcher *parent = 0); + + QQmlWatchProxy(int id, + QQmlExpression *exp, + int debugId, + QQmlWatcher *parent = 0); + +public slots: + void notifyValueChanged(); + +private: + friend class QQmlWatcher; + int m_id; + QQmlWatcher *m_watch; + QObject *m_object; + int m_debugId; + QMetaProperty m_property; + + QQmlExpression *m_expr; +}; + +QQmlWatchProxy::QQmlWatchProxy(int id, + QQmlExpression *exp, + int debugId, + QQmlWatcher *parent) +: QObject(parent), m_id(id), m_watch(parent), m_object(0), m_debugId(debugId), m_expr(exp) +{ + QObject::connect(m_expr, SIGNAL(valueChanged()), this, SLOT(notifyValueChanged())); +} + +QQmlWatchProxy::QQmlWatchProxy(int id, + QObject *object, + int debugId, + const QMetaProperty &prop, + QQmlWatcher *parent) +: QObject(parent), m_id(id), m_watch(parent), m_object(object), m_debugId(debugId), m_property(prop), m_expr(0) +{ + static int refreshIdx = -1; + if(refreshIdx == -1) + refreshIdx = QQmlWatchProxy::staticMetaObject.indexOfMethod("notifyValueChanged()"); + + if (prop.hasNotifySignal()) + QQmlPropertyPrivate::connect(m_object, prop.notifySignalIndex(), this, refreshIdx); +} + +void QQmlWatchProxy::notifyValueChanged() +{ + QVariant v; + if (m_expr) + v = m_expr->evaluate(); + else if (QQmlValueTypeFactory::isValueType(m_property.userType())) + v = m_property.read(m_object); + + emit m_watch->propertyChanged(m_id, m_debugId, m_property, v); +} + + +QQmlWatcher::QQmlWatcher(QObject *parent) + : QObject(parent) +{ +} + +bool QQmlWatcher::addWatch(int id, quint32 debugId) +{ + QObject *object = QQmlDebugService::objectForId(debugId); + if (object) { + int propCount = object->metaObject()->propertyCount(); + for (int ii=0; ii<propCount; ii++) + addPropertyWatch(id, object, debugId, object->metaObject()->property(ii)); + return true; + } + return false; +} + +bool QQmlWatcher::addWatch(int id, quint32 debugId, const QByteArray &property) +{ + QObject *object = QQmlDebugService::objectForId(debugId); + if (object) { + int index = object->metaObject()->indexOfProperty(property.constData()); + if (index >= 0) { + addPropertyWatch(id, object, debugId, object->metaObject()->property(index)); + return true; + } + } + return false; +} + +bool QQmlWatcher::addWatch(int id, quint32 objectId, const QString &expr) +{ + QObject *object = QQmlDebugService::objectForId(objectId); + QQmlContext *context = qmlContext(object); + if (context) { + QQmlExpression *exprObj = new QQmlExpression(context, object, expr); + exprObj->setNotifyOnValueChanged(true); + QQmlWatchProxy *proxy = new QQmlWatchProxy(id, exprObj, objectId, this); + exprObj->setParent(proxy); + m_proxies[id].append(proxy); + proxy->notifyValueChanged(); + return true; + } + return false; +} + +bool QQmlWatcher::removeWatch(int id) +{ + if (!m_proxies.contains(id)) + return false; + + QList<QPointer<QQmlWatchProxy> > proxies = m_proxies.take(id); + qDeleteAll(proxies); + return true; +} + +void QQmlWatcher::addPropertyWatch(int id, QObject *object, quint32 debugId, const QMetaProperty &property) +{ + QQmlWatchProxy *proxy = new QQmlWatchProxy(id, object, debugId, property, this); + m_proxies[id].append(proxy); + + proxy->notifyValueChanged(); +} + +QT_END_NAMESPACE + +#include <qqmlwatcher.moc> diff --git a/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.h b/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.h new file mode 100644 index 0000000000..329aee77d2 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLWATCHER_H +#define QQMLWATCHER_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/qlist.h> +#include <QtCore/qpair.h> +#include <QtCore/qhash.h> +#include <QtCore/qset.h> +#include <QtCore/qpointer.h> + +QT_BEGIN_NAMESPACE + +class QQmlWatchProxy; +class QQmlExpression; +class QQmlContext; +class QMetaProperty; + +class QQmlWatcher : public QObject +{ + Q_OBJECT +public: + QQmlWatcher(QObject * = 0); + + bool addWatch(int id, quint32 objectId); + bool addWatch(int id, quint32 objectId, const QByteArray &property); + bool addWatch(int id, quint32 objectId, const QString &expr); + + bool removeWatch(int id); + +Q_SIGNALS: + void propertyChanged(int id, int objectId, const QMetaProperty &property, const QVariant &value); + +private: + friend class QQmlWatchProxy; + void addPropertyWatch(int id, QObject *object, quint32 objectId, const QMetaProperty &property); + + QHash<int, QList<QPointer<QQmlWatchProxy> > > m_proxies; +}; + +QT_END_NAMESPACE + +#endif // QQMLWATCHER_H diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp new file mode 100644 index 0000000000..4cca1900de --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp @@ -0,0 +1,474 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4datacollector.h" + +#include <private/qv4script_p.h> +#include <private/qv4string_p.h> +#include <private/qv4objectiterator_p.h> +#include <private/qv4identifier_p.h> +#include <private/qv4runtime_p.h> + +#include <QtCore/qjsonarray.h> + +QT_BEGIN_NAMESPACE + +QV4::CallContext *QV4DataCollector::findContext(QV4::ExecutionEngine *engine, int frame) +{ + QV4::ExecutionContext *ctx = engine->currentContext; + while (ctx) { + QV4::CallContext *cCtxt = ctx->asCallContext(); + if (cCtxt && cCtxt->d()->function) { + if (frame < 1) + return cCtxt; + --frame; + } + ctx = engine->parentContext(ctx); + } + + return 0; +} + +QV4::Heap::CallContext *QV4DataCollector::findScope(QV4::ExecutionContext *ctxt, int scope) +{ + if (!ctxt) + return 0; + + QV4::Scope s(ctxt->d()->engine); + QV4::ScopedContext ctx(s, ctxt); + for (; scope > 0 && ctx; --scope) + ctx = ctx->d()->outer; + + return (ctx && ctx->d()) ? ctx->asCallContext()->d() : 0; +} + +QVector<QV4::Heap::ExecutionContext::ContextType> QV4DataCollector::getScopeTypes( + QV4::ExecutionEngine *engine, int frame) +{ + QVector<QV4::Heap::ExecutionContext::ContextType> types; + + QV4::Scope scope(engine); + QV4::CallContext *sctxt = findContext(engine, frame); + if (!sctxt || sctxt->d()->type < QV4::Heap::ExecutionContext::Type_QmlContext) + return types; + + QV4::ScopedContext it(scope, sctxt); + for (; it; it = it->d()->outer) + types.append(it->d()->type); + + return types; +} + + +QV4DataCollector::QV4DataCollector(QV4::ExecutionEngine *engine) + : m_engine(engine), m_collectedRefs(Q_NULLPTR) +{ + values.set(engine, engine->newArrayObject()); +} + +QV4DataCollector::~QV4DataCollector() +{ +} + +void QV4DataCollector::collect(const QV4::ScopedValue &value) +{ + if (m_collectedRefs) + m_collectedRefs->append(addRef(value)); +} + +const QV4::Object *collectProperty(const QV4::ScopedValue &value, QV4::ExecutionEngine *engine, + QJsonObject &dict) +{ + QV4::Scope scope(engine); + QV4::ScopedValue typeString(scope, QV4::Runtime::typeofValue(engine, value)); + dict.insert(QStringLiteral("type"), typeString->toQStringNoThrow()); + + const QLatin1String valueKey("value"); + switch (value->type()) { + case QV4::Value::Empty_Type: + Q_ASSERT(!"empty Value encountered"); + return 0; + case QV4::Value::Undefined_Type: + dict.insert(valueKey, QJsonValue::Undefined); + return 0; + case QV4::Value::Null_Type: + // "null" is not the correct type, but we leave this in until QtC can deal with "object" + dict.insert(QStringLiteral("type"), QStringLiteral("null")); + dict.insert(valueKey, QJsonValue::Null); + return 0; + case QV4::Value::Boolean_Type: + dict.insert(valueKey, value->booleanValue()); + return 0; + case QV4::Value::Managed_Type: + if (const QV4::String *s = value->as<QV4::String>()) { + dict.insert(valueKey, s->toQString()); + } else if (const QV4::ArrayObject *a = value->as<QV4::ArrayObject>()) { + // size of an array is number of its numerical properties; We don't consider free form + // object properties here. + dict.insert(valueKey, qint64(a->getLength())); + return a; + } else if (const QV4::Object *o = value->as<QV4::Object>()) { + int numProperties = 0; + QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly); + QV4::PropertyAttributes attrs; + uint index; + QV4::ScopedProperty p(scope); + QV4::ScopedString name(scope); + while (true) { + it.next(name.getRef(), &index, p, &attrs); + if (attrs.isEmpty()) + break; + else + ++numProperties; + } + dict.insert(valueKey, numProperties); + return o; + } else { + Q_UNREACHABLE(); + } + return 0; + case QV4::Value::Integer_Type: + dict.insert(valueKey, value->integerValue()); + return 0; + default: // double + dict.insert(valueKey, value->doubleValue()); + return 0; + } +} + +QJsonObject QV4DataCollector::lookupRef(Ref ref) +{ + QJsonObject dict; + if (lookupSpecialRef(ref, &dict)) + return dict; + + dict.insert(QStringLiteral("handle"), qint64(ref)); + QV4::Scope scope(engine()); + QV4::ScopedValue value(scope, getValue(ref)); + + if (const QV4::Object *o = collectProperty(value, engine(), dict)) + dict.insert(QStringLiteral("properties"), collectProperties(o)); + + return dict; +} + +QV4DataCollector::Ref QV4DataCollector::addFunctionRef(const QString &functionName) +{ + Ref ref = addRef(QV4::Primitive::emptyValue(), false); + + QJsonObject dict; + dict.insert(QStringLiteral("handle"), qint64(ref)); + dict.insert(QStringLiteral("type"), QStringLiteral("function")); + dict.insert(QStringLiteral("name"), functionName); + specialRefs.insert(ref, dict); + + return ref; +} + +QV4DataCollector::Ref QV4DataCollector::addScriptRef(const QString &scriptName) +{ + Ref ref = addRef(QV4::Primitive::emptyValue(), false); + + QJsonObject dict; + dict.insert(QStringLiteral("handle"), qint64(ref)); + dict.insert(QStringLiteral("type"), QStringLiteral("script")); + dict.insert(QStringLiteral("name"), scriptName); + specialRefs.insert(ref, dict); + + return ref; +} + +void QV4DataCollector::collectScope(QJsonObject *dict, QV4::Debugging::V4Debugger *debugger, + int frameNr, int scopeNr) +{ + QStringList names; + + Refs refs; + if (debugger->state() == QV4::Debugging::V4Debugger::Paused) { + RefHolder holder(this, &refs); + ArgumentCollectJob argumentsJob(m_engine, this, &names, frameNr, scopeNr); + debugger->runInEngine(&argumentsJob); + LocalCollectJob localsJob(m_engine, this, &names, frameNr, scopeNr); + debugger->runInEngine(&localsJob); + } + + QV4::Scope scope(engine()); + QV4::ScopedObject scopeObject(scope, engine()->newObject()); + + Q_ASSERT(names.size() == refs.size()); + for (int i = 0, ei = refs.size(); i != ei; ++i) + scopeObject->put(engine(), names.at(i), + QV4::Value::fromReturnedValue(getValue(refs.at(i)))); + + Ref scopeObjectRef = addRef(scopeObject); + dict->insert(QStringLiteral("ref"), qint64(scopeObjectRef)); + if (m_collectedRefs) + m_collectedRefs->append(scopeObjectRef); +} + +QV4DataCollector::Ref QV4DataCollector::addRef(QV4::Value value, bool deduplicate) +{ + class ExceptionStateSaver + { + quint32 *hasExceptionLoc; + quint32 hadException; + + public: + ExceptionStateSaver(QV4::ExecutionEngine *engine) + : hasExceptionLoc(&engine->hasException) + , hadException(false) + { std::swap(*hasExceptionLoc, hadException); } + + ~ExceptionStateSaver() + { std::swap(*hasExceptionLoc, hadException); } + }; + + // if we wouldn't do this, the putIndexed won't work. + ExceptionStateSaver resetExceptionState(engine()); + QV4::Scope scope(engine()); + QV4::ScopedObject array(scope, values.value()); + if (deduplicate) { + for (Ref i = 0; i < array->getLength(); ++i) { + if (array->getIndexed(i) == value.rawValue() && !specialRefs.contains(i)) + return i; + } + } + Ref ref = array->getLength(); + array->putIndexed(ref, value); + Q_ASSERT(array->getLength() - 1 == ref); + return ref; +} + +QV4::ReturnedValue QV4DataCollector::getValue(Ref ref) +{ + QV4::Scope scope(engine()); + QV4::ScopedObject array(scope, values.value()); + Q_ASSERT(ref < array->getLength()); + return array->getIndexed(ref, Q_NULLPTR); +} + +bool QV4DataCollector::lookupSpecialRef(Ref ref, QJsonObject *dict) +{ + SpecialRefs::const_iterator it = specialRefs.find(ref); + if (it == specialRefs.end()) + return false; + + *dict = it.value(); + return true; +} + +QJsonArray QV4DataCollector::collectProperties(const QV4::Object *object) +{ + QJsonArray res; + + QV4::Scope scope(engine()); + QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly); + QV4::ScopedValue name(scope); + QV4::ScopedValue value(scope); + while (true) { + QV4::Value v; + name = it.nextPropertyNameAsString(&v); + if (name->isNull()) + break; + QString key = name->toQStringNoThrow(); + value = v; + res.append(collectAsJson(key, value)); + } + + return res; +} + +QJsonObject QV4DataCollector::collectAsJson(const QString &name, const QV4::ScopedValue &value) +{ + QJsonObject dict; + if (!name.isNull()) + dict.insert(QStringLiteral("name"), name); + if (value->isManaged() && !value->isString()) { + Ref ref = addRef(value); + dict.insert(QStringLiteral("ref"), qint64(ref)); + if (m_collectedRefs) + m_collectedRefs->append(ref); + } + + collectProperty(value, engine(), dict); + return dict; +} + +ExpressionEvalJob::ExpressionEvalJob(QV4::ExecutionEngine *engine, int frameNr, + const QString &expression, + QV4DataCollector *collector) + : JavaScriptJob(engine, frameNr, expression) + , collector(collector) +{ +} + +void ExpressionEvalJob::handleResult(QV4::ScopedValue &result) +{ + if (hasExeption()) + exception = result->toQStringNoThrow(); + collector->collect(result); +} + +const QString &ExpressionEvalJob::exceptionMessage() const +{ + return exception; +} + +GatherSourcesJob::GatherSourcesJob(QV4::ExecutionEngine *engine, int seq) + : engine(engine) + , seq(seq) +{} + +void GatherSourcesJob::run() +{ + QStringList sources; + + foreach (QV4::CompiledData::CompilationUnit *unit, engine->compilationUnits) { + QString fileName = unit->fileName(); + if (!fileName.isEmpty()) + sources.append(fileName); + } + + QV4::Debugging::V4Debugger *debugger + = static_cast<QV4::Debugging::V4Debugger *>(engine->debugger); + emit debugger->sourcesCollected(debugger, sources, seq); +} + +ArgumentCollectJob::ArgumentCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector, + QStringList *names, int frameNr, int scopeNr) + : engine(engine) + , collector(collector) + , names(names) + , frameNr(frameNr) + , scopeNr(scopeNr) +{} + +void ArgumentCollectJob::run() +{ + if (frameNr < 0) + return; + + QV4::Scope scope(engine); + QV4::Scoped<QV4::CallContext> ctxt( + scope, QV4DataCollector::findScope(QV4DataCollector::findContext(engine, frameNr), scopeNr)); + if (!ctxt) + return; + + QV4::ScopedValue v(scope); + int nFormals = ctxt->formalCount(); + for (unsigned i = 0, ei = nFormals; i != ei; ++i) { + QString qName; + if (QV4::Identifier *name = ctxt->formals()[nFormals - i - 1]) + qName = name->string; + names->append(qName); + v = ctxt->argument(i); + collector->collect(v); + } +} + +LocalCollectJob::LocalCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector, + QStringList *names, int frameNr, int scopeNr) + : engine(engine) + , collector(collector) + , names(names) + , frameNr(frameNr) + , scopeNr(scopeNr) +{} + +void LocalCollectJob::run() +{ + if (frameNr < 0) + return; + + QV4::Scope scope(engine); + QV4::Scoped<QV4::CallContext> ctxt( + scope, QV4DataCollector::findScope(QV4DataCollector::findContext(engine, frameNr), scopeNr)); + if (!ctxt) + return; + + QV4::ScopedValue v(scope); + for (unsigned i = 0, ei = ctxt->variableCount(); i != ei; ++i) { + QString qName; + if (QV4::Identifier *name = ctxt->variables()[i]) + qName = name->string; + names->append(qName); + v = ctxt->d()->locals[i]; + collector->collect(v); + } +} + +ThisCollectJob::ThisCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector, + int frameNr, bool *foundThis) + : engine(engine) + , collector(collector) + , frameNr(frameNr) + , foundThis(foundThis) +{} + +void ThisCollectJob::run() +{ + *foundThis = myRun(); +} + +bool ThisCollectJob::myRun() +{ + QV4::Scope scope(engine); + QV4::ScopedContext ctxt(scope, QV4DataCollector::findContext(engine, frameNr)); + while (ctxt) { + if (QV4::CallContext *cCtxt = ctxt->asCallContext()) + if (cCtxt->d()->activation) + break; + ctxt = ctxt->d()->outer; + } + + if (!ctxt) + return false; + + QV4::ScopedValue o(scope, ctxt->asCallContext()->d()->activation); + collector->collect(o); + return true; +} + +ExceptionCollectJob::ExceptionCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector) + : engine(engine) + , collector(collector) +{} + +void ExceptionCollectJob::run() +{ + QV4::Scope scope(engine); + QV4::ScopedValue v(scope, *engine->exceptionValue); + collector->collect(v); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h new file mode 100644 index 0000000000..d0e4a4ad18 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4DATACOLLECTOR_H +#define QV4DATACOLLECTOR_H + +#include <private/qv4engine_p.h> +#include <private/qv4debugging_p.h> + +QT_BEGIN_NAMESPACE + +class QV4DataCollector +{ +public: + typedef uint Ref; + typedef QVector<uint> Refs; + + static QV4::CallContext *findContext(QV4::ExecutionEngine *engine, int frame); + static QV4::Heap::CallContext *findScope(QV4::ExecutionContext *ctxt, int scope); + static QVector<QV4::Heap::ExecutionContext::ContextType> getScopeTypes( + QV4::ExecutionEngine *engine, int frame); + + QV4DataCollector(QV4::ExecutionEngine *engine); + ~QV4DataCollector(); + + void collect(const QV4::ScopedValue &value); + + QJsonObject lookupRef(Ref ref); + + Ref addFunctionRef(const QString &functionName); + Ref addScriptRef(const QString &scriptName); + + void collectScope(QJsonObject *dict, QV4::Debugging::V4Debugger *debugger, int frameNr, + int scopeNr); + + QV4::ExecutionEngine *engine() const { return m_engine; } + +private: + friend class RefHolder; + + Ref addRef(QV4::Value value, bool deduplicate = true); + QV4::ReturnedValue getValue(Ref ref); + bool lookupSpecialRef(Ref ref, QJsonObject *dict); + + QJsonArray collectProperties(const QV4::Object *object); + QJsonObject collectAsJson(const QString &name, const QV4::ScopedValue &value); + void collectArgumentsInContext(); + + QV4::ExecutionEngine *m_engine; + Refs *m_collectedRefs; + QV4::PersistentValue values; + typedef QHash<Ref, QJsonObject> SpecialRefs; + SpecialRefs specialRefs; +}; + +class RefHolder { +public: + RefHolder(QV4DataCollector *collector, QV4DataCollector::Refs *target) : + m_collector(collector), m_previousRefs(collector->m_collectedRefs) + { + m_collector->m_collectedRefs = target; + } + + ~RefHolder() + { + std::swap(m_collector->m_collectedRefs, m_previousRefs); + } + +private: + QV4DataCollector *m_collector; + QV4DataCollector::Refs *m_previousRefs; +}; + +class ExpressionEvalJob: public QV4::Debugging::V4Debugger::JavaScriptJob +{ + QV4DataCollector *collector; + QString exception; + +public: + ExpressionEvalJob(QV4::ExecutionEngine *engine, int frameNr, const QString &expression, + QV4DataCollector *collector); + virtual void handleResult(QV4::ScopedValue &result); + const QString &exceptionMessage() const; +}; + +class GatherSourcesJob: public QV4::Debugging::V4Debugger::Job +{ + QV4::ExecutionEngine *engine; + const int seq; + +public: + GatherSourcesJob(QV4::ExecutionEngine *engine, int seq); + void run(); +}; + +class ArgumentCollectJob: public QV4::Debugging::V4Debugger::Job +{ + QV4::ExecutionEngine *engine; + QV4DataCollector *collector; + QStringList *names; + int frameNr; + int scopeNr; + +public: + ArgumentCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector, + QStringList *names, int frameNr, int scopeNr); + void run(); +}; + +class LocalCollectJob: public QV4::Debugging::V4Debugger::Job +{ + QV4::ExecutionEngine *engine; + QV4DataCollector *collector; + QStringList *names; + int frameNr; + int scopeNr; + +public: + LocalCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector, QStringList *names, + int frameNr, int scopeNr); + void run(); +}; + +class ThisCollectJob: public QV4::Debugging::V4Debugger::Job +{ + QV4::ExecutionEngine *engine; + QV4DataCollector *collector; + int frameNr; + bool *foundThis; + +public: + ThisCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector, int frameNr, + bool *foundThis); + void run(); + bool myRun(); +}; + +class ExceptionCollectJob: public QV4::Debugging::V4Debugger::Job +{ + QV4::ExecutionEngine *engine; + QV4DataCollector *collector; + +public: + ExceptionCollectJob(QV4::ExecutionEngine *engine, QV4DataCollector *collector); + void run(); +}; + +QT_END_NAMESPACE + +#endif // QV4DATACOLLECTOR_H diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.cpp new file mode 100644 index 0000000000..e33595c629 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.cpp @@ -0,0 +1,253 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4debuggeragent.h" +#include "qv4debugservice.h" +#include "qv4datacollector.h" + +#include <QtCore/qjsonobject.h> +#include <QtCore/qjsonarray.h> + +QT_BEGIN_NAMESPACE + +QV4DebuggerAgent::QV4DebuggerAgent(QV4DebugServiceImpl *debugService) + : m_breakOnThrow(false), m_debugService(debugService) +{} + +QV4::Debugging::V4Debugger *QV4DebuggerAgent::firstDebugger() const +{ + // Currently only 1 single engine is supported, so: + if (m_debuggers.isEmpty()) + return 0; + else + return m_debuggers.first(); +} + +bool QV4DebuggerAgent::isRunning() const +{ + // Currently only 1 single engine is supported, so: + if (QV4::Debugging::V4Debugger *debugger = firstDebugger()) + return debugger->state() == QV4::Debugging::V4Debugger::Running; + else + return false; +} + +void QV4DebuggerAgent::debuggerPaused(QV4::Debugging::V4Debugger *debugger, + QV4::Debugging::PauseReason reason) +{ + Q_UNUSED(reason); + + m_debugService->clearHandles(debugger->engine()); + + QJsonObject event, body, script; + event.insert(QStringLiteral("type"), QStringLiteral("event")); + + switch (reason) { + case QV4::Debugging::Step: + case QV4::Debugging::PauseRequest: + case QV4::Debugging::BreakPoint: { + event.insert(QStringLiteral("event"), QStringLiteral("break")); + QVector<QV4::StackFrame> frames = debugger->stackTrace(1); + if (frames.isEmpty()) + break; + + const QV4::StackFrame &topFrame = frames.first(); + body.insert(QStringLiteral("invocationText"), topFrame.function); + body.insert(QStringLiteral("sourceLine"), topFrame.line - 1); + if (topFrame.column > 0) + body.insert(QStringLiteral("sourceColumn"), topFrame.column); + QJsonArray breakPoints; + foreach (int breakPointId, breakPointIds(topFrame.source, topFrame.line)) + breakPoints.push_back(breakPointId); + body.insert(QStringLiteral("breakpoints"), breakPoints); + script.insert(QStringLiteral("name"), topFrame.source); + } break; + case QV4::Debugging::Throwing: + // TODO: complete this! + event.insert(QStringLiteral("event"), QStringLiteral("exception")); + break; + } + + if (!script.isEmpty()) + body.insert(QStringLiteral("script"), script); + if (!body.isEmpty()) + event.insert(QStringLiteral("body"), body); + m_debugService->send(event); +} + +void QV4DebuggerAgent::sourcesCollected(QV4::Debugging::V4Debugger *debugger, + const QStringList &sources, int requestSequenceNr) +{ + QJsonArray body; + foreach (const QString &source, sources) { + QJsonObject src; + src[QLatin1String("name")] = source; + src[QLatin1String("scriptType")] = 4; + body.append(src); + } + + QJsonObject response; + response[QLatin1String("success")] = true; + response[QLatin1String("running")] = debugger->state() == QV4::Debugging::V4Debugger::Running; + response[QLatin1String("body")] = body; + response[QLatin1String("command")] = QStringLiteral("scripts"); + response[QLatin1String("request_seq")] = requestSequenceNr; + response[QLatin1String("type")] = QStringLiteral("response"); + m_debugService->send(response); +} + +void QV4DebuggerAgent::addDebugger(QV4::Debugging::V4Debugger *debugger) +{ + Q_ASSERT(!m_debuggers.contains(debugger)); + m_debuggers << debugger; + + debugger->setBreakOnThrow(m_breakOnThrow); + + foreach (const BreakPoint &breakPoint, m_breakPoints.values()) + if (breakPoint.enabled) + debugger->addBreakPoint(breakPoint.fileName, breakPoint.lineNr, breakPoint.condition); + + connect(debugger, SIGNAL(destroyed(QObject*)), + this, SLOT(handleDebuggerDeleted(QObject*))); + connect(debugger, SIGNAL(sourcesCollected(QV4::Debugging::V4Debugger*,QStringList,int)), + this, SLOT(sourcesCollected(QV4::Debugging::V4Debugger*,QStringList,int)), + Qt::QueuedConnection); + connect(debugger, + SIGNAL(debuggerPaused(QV4::Debugging::V4Debugger*,QV4::Debugging::PauseReason)), + this, SLOT(debuggerPaused(QV4::Debugging::V4Debugger*,QV4::Debugging::PauseReason)), + Qt::QueuedConnection); +} + +void QV4DebuggerAgent::removeDebugger(QV4::Debugging::V4Debugger *debugger) +{ + m_debuggers.removeAll(debugger); + disconnect(debugger, SIGNAL(destroyed(QObject*)), + this, SLOT(handleDebuggerDeleted(QObject*))); + disconnect(debugger, SIGNAL(sourcesCollected(QV4::Debugging::V4Debugger*,QStringList,int)), + this, SLOT(sourcesCollected(QV4::Debugging::V4Debugger*,QStringList,int))); + disconnect(debugger, + SIGNAL(debuggerPaused(QV4::Debugging::V4Debugger*,QV4::Debugging::PauseReason)), + this, + SLOT(debuggerPaused(QV4::Debugging::V4Debugger*,QV4::Debugging::PauseReason))); +} + +void QV4DebuggerAgent::handleDebuggerDeleted(QObject *debugger) +{ + m_debuggers.removeAll(static_cast<QV4::Debugging::V4Debugger *>(debugger)); +} + +void QV4DebuggerAgent::pause(QV4::Debugging::V4Debugger *debugger) const +{ + debugger->pause(); +} + +void QV4DebuggerAgent::pauseAll() const +{ + foreach (QV4::Debugging::V4Debugger *debugger, m_debuggers) + pause(debugger); +} + +void QV4DebuggerAgent::resumeAll() const +{ + foreach (QV4::Debugging::V4Debugger *debugger, m_debuggers) + if (debugger->state() == QV4::Debugging::V4Debugger::Paused) + debugger->resume(QV4::Debugging::V4Debugger::FullThrottle); +} + +int QV4DebuggerAgent::addBreakPoint(const QString &fileName, int lineNumber, bool enabled, const QString &condition) +{ + if (enabled) + foreach (QV4::Debugging::V4Debugger *debugger, m_debuggers) + debugger->addBreakPoint(fileName, lineNumber, condition); + + int id = m_breakPoints.size(); + m_breakPoints.insert(id, BreakPoint(fileName, lineNumber, enabled, condition)); + return id; +} + +void QV4DebuggerAgent::removeBreakPoint(int id) +{ + BreakPoint breakPoint = m_breakPoints.value(id); + if (!breakPoint.isValid()) + return; + + m_breakPoints.remove(id); + + if (breakPoint.enabled) + foreach (QV4::Debugging::V4Debugger *debugger, m_debuggers) + debugger->removeBreakPoint(breakPoint.fileName, breakPoint.lineNr); +} + +void QV4DebuggerAgent::removeAllBreakPoints() +{ + QList<int> ids = m_breakPoints.keys(); + foreach (int id, ids) + removeBreakPoint(id); +} + +void QV4DebuggerAgent::enableBreakPoint(int id, bool onoff) +{ + BreakPoint &breakPoint = m_breakPoints[id]; + if (!breakPoint.isValid() || breakPoint.enabled == onoff) + return; + breakPoint.enabled = onoff; + + foreach (QV4::Debugging::V4Debugger *debugger, m_debuggers) { + if (onoff) + debugger->addBreakPoint(breakPoint.fileName, breakPoint.lineNr, breakPoint.condition); + else + debugger->removeBreakPoint(breakPoint.fileName, breakPoint.lineNr); + } +} + +QList<int> QV4DebuggerAgent::breakPointIds(const QString &fileName, int lineNumber) const +{ + QList<int> ids; + + for (QHash<int, BreakPoint>::const_iterator i = m_breakPoints.begin(), ei = m_breakPoints.end(); i != ei; ++i) + if (i->lineNr == lineNumber && fileName.endsWith(i->fileName)) + ids.push_back(i.key()); + + return ids; +} + +void QV4DebuggerAgent::setBreakOnThrow(bool onoff) +{ + if (onoff != m_breakOnThrow) { + m_breakOnThrow = onoff; + foreach (QV4::Debugging::V4Debugger *debugger, m_debuggers) + debugger->setBreakOnThrow(onoff); + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.h b/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.h new file mode 100644 index 0000000000..eafb408e7a --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4DEBUGGERAGENT_H +#define QV4DEBUGGERAGENT_H + +#include <private/qv4debugging_p.h> + +QT_BEGIN_NAMESPACE + +class QV4DebugServiceImpl; + +class QV4DebuggerAgent : public QObject +{ + Q_OBJECT +public: + QV4DebuggerAgent(QV4DebugServiceImpl *m_debugService); + + QV4::Debugging::V4Debugger *firstDebugger() const; + bool isRunning() const; + + void addDebugger(QV4::Debugging::V4Debugger *debugger); + void removeDebugger(QV4::Debugging::V4Debugger *debugger); + + void pause(QV4::Debugging::V4Debugger *debugger) const; + void pauseAll() const; + void resumeAll() const; + int addBreakPoint(const QString &fileName, int lineNumber, bool enabled = true, const QString &condition = QString()); + void removeBreakPoint(int id); + void removeAllBreakPoints(); + void enableBreakPoint(int id, bool onoff); + QList<int> breakPointIds(const QString &fileName, int lineNumber) const; + + bool breakOnThrow() const { return m_breakOnThrow; } + void setBreakOnThrow(bool onoff); + +public slots: + void debuggerPaused(QV4::Debugging::V4Debugger *debugger, QV4::Debugging::PauseReason reason); + void sourcesCollected(QV4::Debugging::V4Debugger *debugger, const QStringList &sources, + int requestSequenceNr); + void handleDebuggerDeleted(QObject *debugger); + +private: + QList<QV4::Debugging::V4Debugger *> m_debuggers; + + struct BreakPoint { + QString fileName; + int lineNr; + bool enabled; + QString condition; + + BreakPoint(): lineNr(-1), enabled(false) {} + BreakPoint(const QString &fileName, int lineNr, bool enabled, const QString &condition) + : fileName(fileName), lineNr(lineNr), enabled(enabled), condition(condition) + {} + + bool isValid() const { return lineNr >= 0 && !fileName.isEmpty(); } + }; + + QHash<int, BreakPoint> m_breakPoints; + bool m_breakOnThrow; + QV4DebugServiceImpl *m_debugService; +}; + +QT_END_NAMESPACE + +#endif // QV4DEBUGGERAGENT_H diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp new file mode 100644 index 0000000000..5233a09992 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp @@ -0,0 +1,931 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4debugservice.h" +#include "qqmlengine.h" +#include <private/qv4engine_p.h> +#include <private/qv4isel_moth_p.h> +#include <private/qv4function_p.h> +#include <private/qqmldebugconnector_p.h> + +#include <private/qv8engine_p.h> + +#include <QtCore/QJsonArray> +#include <QtCore/QJsonDocument> +#include <QtCore/QJsonObject> +#include <QtCore/QJsonValue> + +const char *const V4_CONNECT = "connect"; +const char *const V4_DISCONNECT = "disconnect"; +const char *const V4_BREAK_ON_SIGNAL = "breakonsignal"; +const char *const V4_PAUSE = "interrupt"; + +#define NO_PROTOCOL_TRACING +#ifdef NO_PROTOCOL_TRACING +# define TRACE_PROTOCOL(x) +#else +#include <QtCore/QDebug> +# define TRACE_PROTOCOL(x) x +#endif + +QT_BEGIN_NAMESPACE + +class V8CommandHandler; +class UnknownV8CommandHandler; + +int QV4DebugServiceImpl::debuggerIndex = 0; +int QV4DebugServiceImpl::sequence = 0; + +class V8CommandHandler +{ +public: + V8CommandHandler(const QString &command) + : cmd(command) + {} + + virtual ~V8CommandHandler() + {} + + QString command() const { return cmd; } + + void handle(const QJsonObject &request, QV4DebugServiceImpl *s) + { + TRACE_PROTOCOL(qDebug() << "handling command" << command() << "..."); + + req = request; + seq = req.value(QStringLiteral("seq")); + debugService = s; + + handleRequest(); + if (!response.isEmpty()) { + response[QLatin1String("type")] = QStringLiteral("response"); + debugService->send(response); + } + + debugService = 0; + seq = QJsonValue(); + req = QJsonObject(); + response = QJsonObject(); + } + + virtual void handleRequest() = 0; + +protected: + void addCommand() { response.insert(QStringLiteral("command"), cmd); } + void addRequestSequence() { response.insert(QStringLiteral("request_seq"), seq); } + void addSuccess(bool success) { response.insert(QStringLiteral("success"), success); } + void addBody(const QJsonObject &body) + { + response.insert(QStringLiteral("body"), body); + } + + void addRunning() + { + response.insert(QStringLiteral("running"), debugService->debuggerAgent.isRunning()); + } + + void addRefs() + { + response.insert(QStringLiteral("refs"), debugService->buildRefs()); + } + + void createErrorResponse(const QString &msg) + { + QJsonValue command = req.value(QStringLiteral("command")); + response.insert(QStringLiteral("command"), command); + addRequestSequence(); + addSuccess(false); + addRunning(); + response.insert(QStringLiteral("message"), msg); + } + + int requestSequenceNr() const + { return seq.toInt(-1); } + +protected: + QString cmd; + QJsonObject req; + QJsonValue seq; + QV4DebugServiceImpl *debugService; + QJsonObject response; +}; + +class UnknownV8CommandHandler: public V8CommandHandler +{ +public: + UnknownV8CommandHandler(): V8CommandHandler(QString()) {} + + virtual void handleRequest() + { + QString msg = QStringLiteral("unimplemented command \""); + msg += req.value(QStringLiteral("command")).toString(); + msg += QLatin1Char('"'); + createErrorResponse(msg); + } +}; + +namespace { +class V8VersionRequest: public V8CommandHandler +{ +public: + V8VersionRequest(): V8CommandHandler(QStringLiteral("version")) {} + + virtual void handleRequest() + { + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + QJsonObject body; + body.insert(QStringLiteral("V8Version"), + QLatin1String("this is not V8, this is V4 in Qt " QT_VERSION_STR)); + addBody(body); + } +}; + +class V8SetBreakPointRequest: public V8CommandHandler +{ +public: + V8SetBreakPointRequest(): V8CommandHandler(QStringLiteral("setbreakpoint")) {} + + virtual void handleRequest() + { + // decypher the payload: + QJsonObject args = req.value(QStringLiteral("arguments")).toObject(); + if (args.isEmpty()) + return; + + QString type = args.value(QStringLiteral("type")).toString(); + if (type != QStringLiteral("scriptRegExp")) { + createErrorResponse(QStringLiteral("breakpoint type \"%1\" is not implemented").arg(type)); + return; + } + + QString fileName = args.value(QStringLiteral("target")).toString(); + if (fileName.isEmpty()) { + createErrorResponse(QStringLiteral("breakpoint has no file name")); + return; + } + + int line = args.value(QStringLiteral("line")).toInt(-1); + if (line < 0) { + createErrorResponse(QStringLiteral("breakpoint has an invalid line number")); + return; + } + + bool enabled = args.value(QStringLiteral("enabled")).toBool(true); + QString condition = args.value(QStringLiteral("condition")).toString(); + + // set the break point: + int id = debugService->debuggerAgent.addBreakPoint(fileName, line + 1, enabled, condition); + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + QJsonObject body; + body.insert(QStringLiteral("type"), type); + body.insert(QStringLiteral("breakpoint"), id); + // It's undocumented, but V8 sends back an actual_locations array too. However, our + // Debugger currently doesn't tell us when it resolved a breakpoint, so we'll leave them + // pending until the breakpoint is hit for the first time. + addBody(body); + } +}; + +class V8ClearBreakPointRequest: public V8CommandHandler +{ +public: + V8ClearBreakPointRequest(): V8CommandHandler(QStringLiteral("clearbreakpoint")) {} + + virtual void handleRequest() + { + // decypher the payload: + QJsonObject args = req.value(QStringLiteral("arguments")).toObject(); + if (args.isEmpty()) + return; + + int id = args.value(QStringLiteral("breakpoint")).toInt(-1); + if (id < 0) { + createErrorResponse(QStringLiteral("breakpoint has an invalid number")); + return; + } + + // remove the break point: + debugService->debuggerAgent.removeBreakPoint(id); + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + QJsonObject body; + body.insert(QStringLiteral("type"), QStringLiteral("scriptRegExp")); + body.insert(QStringLiteral("breakpoint"), id); + addBody(body); + } +}; + +class V8BacktraceRequest: public V8CommandHandler +{ +public: + V8BacktraceRequest(): V8CommandHandler(QStringLiteral("backtrace")) {} + + virtual void handleRequest() + { + // decypher the payload: + + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + int fromFrame = arguments.value(QStringLiteral("fromFrame")).toInt(0); + int toFrame = arguments.value(QStringLiteral("toFrame")).toInt(fromFrame + 10); + // no idea what the bottom property is for, so we'll ignore it. + + QV4::Debugging::V4Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + + QJsonArray frameArray; + QVector<QV4::StackFrame> frames = debugger->stackTrace(toFrame); + for (int i = fromFrame; i < toFrame && i < frames.size(); ++i) + frameArray.push_back(debugService->buildFrame(frames[i], i, debugger)); + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + QJsonObject body; + if (frameArray.isEmpty()) { + body.insert(QStringLiteral("totalFrames"), 0); + } else { + body.insert(QStringLiteral("fromFrame"), fromFrame); + body.insert(QStringLiteral("toFrame"), fromFrame + frameArray.size()); + body.insert(QStringLiteral("frames"), frameArray); + } + addBody(body); + addRefs(); + } +}; + +class V8FrameRequest: public V8CommandHandler +{ +public: + V8FrameRequest(): V8CommandHandler(QStringLiteral("frame")) {} + + virtual void handleRequest() + { + // decypher the payload: + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + const int frameNr = arguments.value(QStringLiteral("number")).toInt( + debugService->selectedFrame()); + + QV4::Debugging::V4Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + QVector<QV4::StackFrame> frames = debugger->stackTrace(frameNr + 1); + if (frameNr < 0 || frameNr >= frames.size()) { + createErrorResponse(QStringLiteral("frame command has invalid frame number")); + return; + } + + debugService->selectFrame(frameNr); + QJsonObject frame = debugService->buildFrame(frames[frameNr], frameNr, debugger); + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + addBody(frame); + addRefs(); + } +}; + +class V8ScopeRequest: public V8CommandHandler +{ +public: + V8ScopeRequest(): V8CommandHandler(QStringLiteral("scope")) {} + + virtual void handleRequest() + { + // decypher the payload: + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + const int frameNr = arguments.value(QStringLiteral("frameNumber")).toInt( + debugService->selectedFrame()); + const int scopeNr = arguments.value(QStringLiteral("number")).toInt(0); + + QV4::Debugging::V4Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + QVector<QV4::StackFrame> frames = debugger->stackTrace(frameNr + 1); + if (frameNr < 0 || frameNr >= frames.size()) { + createErrorResponse(QStringLiteral("scope command has invalid frame number")); + return; + } + if (scopeNr < 0) { + createErrorResponse(QStringLiteral("scope command has invalid scope number")); + return; + } + + QJsonObject scope = debugService->buildScope(frameNr, scopeNr, debugger); + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + addBody(scope); + addRefs(); + } +}; + +class V8LookupRequest: public V8CommandHandler +{ +public: + V8LookupRequest(): V8CommandHandler(QStringLiteral("lookup")) {} + + virtual void handleRequest() + { + // decypher the payload: + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + QJsonArray handles = arguments.value(QStringLiteral("handles")).toArray(); + + QJsonObject body; + foreach (const QJsonValue &handle, handles) + body[QString::number(handle.toInt())] = debugService->lookup(handle.toInt()); + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + addBody(body); + addRefs(); + } +}; + +class V8ContinueRequest: public V8CommandHandler +{ +public: + V8ContinueRequest(): V8CommandHandler(QStringLiteral("continue")) {} + + virtual void handleRequest() + { + // decypher the payload: + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + + QV4::Debugging::V4Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + + if (arguments.empty()) { + debugger->resume(QV4::Debugging::V4Debugger::FullThrottle); + } else { + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + QString stepAction = arguments.value(QStringLiteral("stepaction")).toString(); + const int stepcount = arguments.value(QStringLiteral("stepcount")).toInt(1); + if (stepcount != 1) + qWarning() << "Step count other than 1 is not supported."; + + if (stepAction == QStringLiteral("in")) { + debugger->resume(QV4::Debugging::V4Debugger::StepIn); + } else if (stepAction == QStringLiteral("out")) { + debugger->resume(QV4::Debugging::V4Debugger::StepOut); + } else if (stepAction == QStringLiteral("next")) { + debugger->resume(QV4::Debugging::V4Debugger::StepOver); + } else { + createErrorResponse(QStringLiteral("continue command has invalid stepaction")); + return; + } + } + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + } +}; + +class V8DisconnectRequest: public V8CommandHandler +{ +public: + V8DisconnectRequest(): V8CommandHandler(QStringLiteral("disconnect")) {} + + virtual void handleRequest() + { + debugService->debuggerAgent.removeAllBreakPoints(); + debugService->debuggerAgent.resumeAll(); + + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + } +}; + +class V8SetExceptionBreakRequest: public V8CommandHandler +{ +public: + V8SetExceptionBreakRequest(): V8CommandHandler(QStringLiteral("setexceptionbreak")) {} + + virtual void handleRequest() + { + bool wasEnabled = debugService->debuggerAgent.breakOnThrow(); + + //decypher the payload: + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + QString type = arguments.value(QStringLiteral("type")).toString(); + bool enabled = arguments.value(QStringLiteral("number")).toBool(!wasEnabled); + + if (type == QStringLiteral("all")) { + // that's fine + } else if (type == QStringLiteral("uncaught")) { + createErrorResponse(QStringLiteral("breaking only on uncaught exceptions is not supported yet")); + return; + } else { + createErrorResponse(QStringLiteral("invalid type for break on exception")); + return; + } + + // do it: + debugService->debuggerAgent.setBreakOnThrow(enabled); + + QJsonObject body; + body[QLatin1String("type")] = type; + body[QLatin1String("enabled")] = debugService->debuggerAgent.breakOnThrow(); + + // response: + addBody(body); + addRunning(); + addSuccess(true); + addRequestSequence(); + addCommand(); + } +}; + +class V8ScriptsRequest: public V8CommandHandler +{ +public: + V8ScriptsRequest(): V8CommandHandler(QStringLiteral("scripts")) {} + + virtual void handleRequest() + { + //decypher the payload: + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + int types = arguments.value(QStringLiteral("types")).toInt(-1); + if (types < 0 || types > 7) { + createErrorResponse(QStringLiteral("invalid types value in scripts command")); + return; + } else if (types != 4) { + createErrorResponse(QStringLiteral("unsupported types value in scripts command")); + return; + } + + // do it: + QV4::Debugging::V4Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + GatherSourcesJob job(debugger->engine(), requestSequenceNr()); + debugger->runInEngine(&job); + + // response will be send by + } +}; + +// Request: +// { +// "seq": 4, +// "type": "request", +// "command": "evaluate", +// "arguments": { +// "expression": "a", +// "frame": 0 +// } +// } +// +// Response: +// { +// "body": { +// "handle": 3, +// "type": "number", +// "value": 1 +// }, +// "command": "evaluate", +// "refs": [], +// "request_seq": 4, +// "running": false, +// "seq": 5, +// "success": true, +// "type": "response" +// } +// +// The "value" key in "body" is the result of evaluating the expression in the request. +class V8EvaluateRequest: public V8CommandHandler +{ +public: + V8EvaluateRequest(): V8CommandHandler(QStringLiteral("evaluate")) {} + + virtual void handleRequest() + { + QV4::Debugging::V4Debugger *debugger = debugService->debuggerAgent.firstDebugger(); + if (debugger->state() == QV4::Debugging::V4Debugger::Paused) { + QJsonObject arguments = req.value(QStringLiteral("arguments")).toObject(); + QString expression = arguments.value(QStringLiteral("expression")).toString(); + const int frame = arguments.value(QStringLiteral("frame")).toInt(0); + + QV4DataCollector *collector = debugService->collector(); + RefHolder holder(collector, debugService->refs()); + ExpressionEvalJob job(debugger->engine(), frame, expression, collector); + debugger->runInEngine(&job); + if (job.hasExeption()) { + createErrorResponse(job.exceptionMessage()); + } else { + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + addBody(collector->lookupRef(debugService->refs()->last())); + addRefs(); + } + } else { + createErrorResponse(QStringLiteral("Debugger has to be paused for evaluate to work.")); + } + } +}; +} // anonymous namespace + +void QV4DebugServiceImpl::addHandler(V8CommandHandler* handler) +{ + handlers[handler->command()] = handler; +} + +V8CommandHandler *QV4DebugServiceImpl::v8CommandHandler(const QString &command) const +{ + V8CommandHandler *handler = handlers.value(command, 0); + if (handler) + return handler; + else + return unknownV8CommandHandler.data(); +} + +QV4DebugServiceImpl::QV4DebugServiceImpl(QObject *parent) : + QQmlConfigurableDebugService<QV4DebugService>(1, parent), + debuggerAgent(this), version(1), theSelectedFrame(0), + unknownV8CommandHandler(new UnknownV8CommandHandler) +{ + addHandler(new V8VersionRequest); + addHandler(new V8SetBreakPointRequest); + addHandler(new V8ClearBreakPointRequest); + addHandler(new V8BacktraceRequest); + addHandler(new V8FrameRequest); + addHandler(new V8ScopeRequest); + addHandler(new V8LookupRequest); + addHandler(new V8ContinueRequest); + addHandler(new V8DisconnectRequest); + addHandler(new V8SetExceptionBreakRequest); + addHandler(new V8ScriptsRequest); + addHandler(new V8EvaluateRequest); +} + +QV4DebugServiceImpl::~QV4DebugServiceImpl() +{ + qDeleteAll(handlers); +} + +void QV4DebugServiceImpl::engineAboutToBeAdded(QQmlEngine *engine) +{ + QMutexLocker lock(&m_configMutex); + if (engine) { + QV4::ExecutionEngine *ee = QV8Engine::getV4(engine->handle()); + if (QQmlDebugConnector *server = QQmlDebugConnector::instance()) { + if (ee) { + ee->iselFactory.reset(new QV4::Moth::ISelFactory); + QV4::Debugging::V4Debugger *debugger = new QV4::Debugging::V4Debugger(ee); + if (state() == Enabled) + ee->setDebugger(debugger); + debuggerMap.insert(debuggerIndex++, debugger); + debuggerAgent.addDebugger(debugger); + debuggerAgent.moveToThread(server->thread()); + } + } + } + QQmlConfigurableDebugService<QV4DebugService>::engineAboutToBeAdded(engine); +} + +void QV4DebugServiceImpl::engineAboutToBeRemoved(QQmlEngine *engine) +{ + QMutexLocker lock(&m_configMutex); + if (engine){ + const QV4::ExecutionEngine *ee = QV8Engine::getV4(engine->handle()); + if (ee) { + QV4::Debugging::V4Debugger *debugger + = qobject_cast<QV4::Debugging::V4Debugger *>(ee->debugger); + if (debugger) { + typedef QMap<int, QV4::Debugging::V4Debugger *>::const_iterator DebuggerMapIterator; + const DebuggerMapIterator end = debuggerMap.constEnd(); + for (DebuggerMapIterator i = debuggerMap.constBegin(); i != end; ++i) { + if (i.value() == debugger) { + debuggerMap.remove(i.key()); + break; + } + } + debuggerAgent.removeDebugger(debugger); + } + } + } + QQmlConfigurableDebugService<QV4DebugService>::engineAboutToBeRemoved(engine); +} + +void QV4DebugServiceImpl::stateAboutToBeChanged(State state) +{ + QMutexLocker lock(&m_configMutex); + if (state == Enabled) { + typedef QMap<int, QV4::Debugging::V4Debugger *>::const_iterator DebuggerMapIterator; + const DebuggerMapIterator end = debuggerMap.constEnd(); + for (DebuggerMapIterator i = debuggerMap.constBegin(); i != end; ++i) { + QV4::ExecutionEngine *ee = i.value()->engine(); + if (!ee->debugger) + ee->setDebugger(i.value()); + } + } + QQmlConfigurableDebugService<QV4DebugService>::stateAboutToBeChanged(state); +} + +void QV4DebugServiceImpl::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. + + //Parse just the name and remove the class info + //Normalize to Lower case. + QString signalName = signal.left(signal.indexOf(QLatin1Char('('))).toLower(); + + foreach (const QString &signal, breakOnSignals) { + if (signal == signalName) { + // TODO: pause debugger + break; + } + } +} + +void QV4DebugServiceImpl::messageReceived(const QByteArray &message) +{ + QMutexLocker lock(&m_configMutex); + + QQmlDebugStream ms(message); + QByteArray header; + ms >> header; + + TRACE_PROTOCOL(qDebug() << "received message with header" << header); + + if (header == "V8DEBUG") { + QByteArray type; + QByteArray payload; + ms >> type >> payload; + TRACE_PROTOCOL(qDebug() << "... type:" << type); + + if (type == V4_CONNECT) { + emit messageToClient(name(), packMessage(type)); + stopWaiting(); + } else if (type == V4_PAUSE) { + debuggerAgent.pauseAll(); + sendSomethingToSomebody(type); + } else if (type == V4_BREAK_ON_SIGNAL) { + QByteArray signal; + bool enabled; + ms >> signal >> enabled; + //Normalize to lower case. + QString signalName(QString::fromUtf8(signal).toLower()); + if (enabled) + breakOnSignals.append(signalName); + else + breakOnSignals.removeOne(signalName); + } else if (type == "v8request") { + handleV8Request(payload); + } else if (type == V4_DISCONNECT) { + TRACE_PROTOCOL(qDebug() << "... payload:" << payload.constData()); + handleV8Request(payload); + } else { + sendSomethingToSomebody(type, 0); + } + } +} + +void QV4DebugServiceImpl::sendSomethingToSomebody(const char *type, int magicNumber) +{ + QByteArray response; + QQmlDebugStream rs(&response, QIODevice::WriteOnly); + rs << QByteArray(type) + << QByteArray::number(version) << QByteArray::number(magicNumber); + emit messageToClient(name(), packMessage(type, response)); +} + +void QV4DebugServiceImpl::handleV8Request(const QByteArray &payload) +{ + TRACE_PROTOCOL(qDebug() << "v8request, payload:" << payload.constData()); + + QJsonDocument request = QJsonDocument::fromJson(payload); + QJsonObject o = request.object(); + QJsonValue type = o.value(QStringLiteral("type")); + if (type.toString() == QStringLiteral("request")) { + QJsonValue command = o.value(QStringLiteral("command")); + V8CommandHandler *h = v8CommandHandler(command.toString()); + if (h) + h->handle(o, this); + } +} + +QByteArray QV4DebugServiceImpl::packMessage(const QByteArray &command, const QByteArray &message) +{ + QByteArray reply; + QQmlDebugStream rs(&reply, QIODevice::WriteOnly); + static const QByteArray cmd("V8DEBUG"); + rs << cmd << command << message; + return reply; +} + +void QV4DebugServiceImpl::send(QJsonObject v8Payload) +{ + v8Payload[QLatin1String("seq")] = sequence++; + QJsonDocument doc; + doc.setObject(v8Payload); +#ifdef NO_PROTOCOL_TRACING + QByteArray responseData = doc.toJson(QJsonDocument::Compact); +#else + QByteArray responseData = doc.toJson(QJsonDocument::Indented); +#endif + + TRACE_PROTOCOL(qDebug() << "sending response for:" << responseData.constData() << endl); + + emit messageToClient(name(), packMessage("v8message", responseData)); +} + +void QV4DebugServiceImpl::clearHandles(QV4::ExecutionEngine *engine) +{ + theCollector.reset(new QV4DataCollector(engine)); +} + +QJsonObject QV4DebugServiceImpl::buildFrame(const QV4::StackFrame &stackFrame, int frameNr, + QV4::Debugging::V4Debugger *debugger) +{ + QV4DataCollector::Ref ref; + + QJsonObject frame; + frame[QLatin1String("index")] = frameNr; + frame[QLatin1String("debuggerFrame")] = false; + ref = theCollector->addFunctionRef(stackFrame.function); + collectedRefs.append(ref); + frame[QLatin1String("func")] = toRef(ref); + ref = theCollector->addScriptRef(stackFrame.source); + collectedRefs.append(ref); + frame[QLatin1String("script")] = toRef(ref); + frame[QLatin1String("line")] = stackFrame.line - 1; + if (stackFrame.column >= 0) + frame[QLatin1String("column")] = stackFrame.column; + + QJsonArray scopes; + if (debugger->state() == QV4::Debugging::V4Debugger::Paused) { + RefHolder holder(theCollector.data(), &collectedRefs); + bool foundThis = false; + ThisCollectJob job(debugger->engine(), theCollector.data(), frameNr, &foundThis); + debugger->runInEngine(&job); + if (foundThis) + frame[QLatin1String("receiver")] = toRef(collectedRefs.last()); + + // Only type and index are used by Qt Creator, so we keep it easy: + QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes = + QV4DataCollector::getScopeTypes(debugger->engine(), frameNr); + for (int i = 0, ei = scopeTypes.count(); i != ei; ++i) { + int type = encodeScopeType(scopeTypes[i]); + if (type == -1) + continue; + + QJsonObject scope; + scope[QLatin1String("index")] = i; + scope[QLatin1String("type")] = type; + scopes.push_back(scope); + } + } + frame[QLatin1String("scopes")] = scopes; + + return frame; +} + +int QV4DebugServiceImpl::encodeScopeType(QV4::Heap::ExecutionContext::ContextType scopeType) +{ + switch (scopeType) { + case QV4::Heap::ExecutionContext::Type_GlobalContext: + return 0; + break; + case QV4::Heap::ExecutionContext::Type_CatchContext: + return 4; + break; + case QV4::Heap::ExecutionContext::Type_WithContext: + return 2; + break; + case QV4::Heap::ExecutionContext::Type_SimpleCallContext: + case QV4::Heap::ExecutionContext::Type_CallContext: + return 1; + break; + case QV4::Heap::ExecutionContext::Type_QmlContext: + default: + return -1; + } +} + +QJsonObject QV4DebugServiceImpl::buildScope(int frameNr, int scopeNr, + QV4::Debugging::V4Debugger *debugger) +{ + QJsonObject scope; + + QJsonObject object; + RefHolder holder(theCollector.data(), &collectedRefs); + theCollector->collectScope(&object, debugger, frameNr, scopeNr); + + if (debugger->state() == QV4::Debugging::V4Debugger::Paused) { + QVector<QV4::Heap::ExecutionContext::ContextType> scopeTypes = + QV4DataCollector::getScopeTypes(debugger->engine(), frameNr); + scope[QLatin1String("type")] = encodeScopeType(scopeTypes[scopeNr]); + } else { + scope[QLatin1String("type")] = -1; + } + scope[QLatin1String("index")] = scopeNr; + scope[QLatin1String("frameIndex")] = frameNr; + scope[QLatin1String("object")] = object; + + return scope; +} + +QJsonValue QV4DebugServiceImpl::lookup(QV4DataCollector::Ref refId) +{ + RefHolder holder(theCollector.data(), &collectedRefs); + return theCollector->lookupRef(refId); +} + +QJsonArray QV4DebugServiceImpl::buildRefs() +{ + QJsonArray refs; + std::sort(collectedRefs.begin(), collectedRefs.end()); + for (int i = 0, ei = collectedRefs.size(); i != ei; ++i) { + QV4DataCollector::Ref ref = collectedRefs.at(i); + if (i > 0 && ref == collectedRefs.at(i - 1)) + continue; + refs.append(lookup(ref)); + } + + collectedRefs.clear(); + return refs; +} + +QJsonValue QV4DebugServiceImpl::toRef(QV4DataCollector::Ref ref) +{ + QJsonObject dict; + dict.insert(QStringLiteral("ref"), qint64(ref)); + return dict; +} + +QV4DataCollector *QV4DebugServiceImpl::collector() const +{ + return theCollector.data(); +} + +QV4DataCollector::Refs *QV4DebugServiceImpl::refs() +{ + return &collectedRefs; +} + +void QV4DebugServiceImpl::selectFrame(int frameNr) +{ + theSelectedFrame = frameNr; +} + +int QV4DebugServiceImpl::selectedFrame() const +{ + return theSelectedFrame; +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h new file mode 100644 index 0000000000..273e5ffd62 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4DEBUGSERVICE_H +#define QV4DEBUGSERVICE_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 "qqmlconfigurabledebugservice.h" +#include "qv4debuggeragent.h" +#include "qv4datacollector.h" +#include <private/qqmldebugserviceinterfaces_p.h> +#include <private/qv4debugging_p.h> + +#include <QtCore/QJsonValue> + +QT_BEGIN_NAMESPACE + +namespace QV4 { struct ExecutionEngine; } + +class QQmlEngine; +class VariableCollector; +class V8CommandHandler; +class UnknownV8CommandHandler; +class QV4DebugServiceImpl; + +class QV4DebugServiceImpl : public QQmlConfigurableDebugService<QV4DebugService> +{ + Q_OBJECT +public: + explicit QV4DebugServiceImpl(QObject *parent = 0); + ~QV4DebugServiceImpl(); + + void engineAboutToBeAdded(QQmlEngine *engine); + void engineAboutToBeRemoved(QQmlEngine *engine); + + void stateAboutToBeChanged(State state); + + void signalEmitted(const QString &signal); + void send(QJsonObject v8Payload); + + QJsonObject buildScope(int frameNr, int scopeNr, QV4::Debugging::V4Debugger *debugger); + QJsonArray buildRefs(); + QJsonValue lookup(QV4DataCollector::Ref refId); + QJsonValue toRef(QV4DataCollector::Ref ref); + + QJsonObject buildFrame(const QV4::StackFrame &stackFrame, int frameNr, + QV4::Debugging::V4Debugger *debugger); + int selectedFrame() const; + void selectFrame(int frameNr); + + void clearHandles(QV4::ExecutionEngine *engine); + + QV4DataCollector *collector() const; + QV4DebuggerAgent debuggerAgent; + QV4DataCollector::Refs *refs(); + +protected: + void messageReceived(const QByteArray &); + void sendSomethingToSomebody(const char *type, int magicNumber = 1); + +private: + friend class QQmlDebuggerServiceFactory; + + void handleV8Request(const QByteArray &payload); + static QByteArray packMessage(const QByteArray &command, + const QByteArray &message = QByteArray()); + void processCommand(const QByteArray &command, const QByteArray &data); + V8CommandHandler *v8CommandHandler(const QString &command) const; + int encodeScopeType(QV4::Heap::ExecutionContext::ContextType scopeType); + + QStringList breakOnSignals; + QMap<int, QV4::Debugging::V4Debugger *> debuggerMap; + static int debuggerIndex; + static int sequence; + const int version; + QV4DataCollector::Refs collectedRefs; + + QScopedPointer<QV4DataCollector> theCollector; + int theSelectedFrame; + + void addHandler(V8CommandHandler* handler); + QHash<QString, V8CommandHandler*> handlers; + QScopedPointer<UnknownV8CommandHandler> unknownV8CommandHandler; +}; + +QT_END_NAMESPACE + +#endif // QV4DEBUGSERVICE_H diff --git a/src/plugins/qmltooling/shared/abstracttool.cpp b/src/plugins/qmltooling/qmldbg_inspector/abstracttool.cpp index 990bc704b9..3e059bed13 100644 --- a/src/plugins/qmltooling/shared/abstracttool.cpp +++ b/src/plugins/qmltooling/qmldbg_inspector/abstracttool.cpp @@ -35,6 +35,8 @@ #include "abstractviewinspector.h" +QT_BEGIN_NAMESPACE + namespace QmlJSDebugger { AbstractTool::AbstractTool(AbstractViewInspector *inspector) : @@ -44,3 +46,5 @@ AbstractTool::AbstractTool(AbstractViewInspector *inspector) : } } // namespace QmlJSDebugger + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/shared/abstracttool.h b/src/plugins/qmltooling/qmldbg_inspector/abstracttool.h index 85f2b5b9ad..c796925866 100644 --- a/src/plugins/qmltooling/shared/abstracttool.h +++ b/src/plugins/qmltooling/qmldbg_inspector/abstracttool.h @@ -41,7 +41,6 @@ class QMouseEvent; class QKeyEvent; class QWheelEvent; class QTouchEvent; -QT_END_NAMESPACE namespace QmlJSDebugger { @@ -81,4 +80,6 @@ private: } // namespace QmlJSDebugger +QT_END_NAMESPACE + #endif // ABSTRACTTOOL_H diff --git a/src/plugins/qmltooling/shared/abstractviewinspector.cpp b/src/plugins/qmltooling/qmldbg_inspector/abstractviewinspector.cpp index e718cf022f..fa6dca7aca 100644 --- a/src/plugins/qmltooling/shared/abstractviewinspector.cpp +++ b/src/plugins/qmltooling/qmldbg_inspector/abstractviewinspector.cpp @@ -32,14 +32,13 @@ ****************************************************************************/ #include "abstractviewinspector.h" - #include "abstracttool.h" #include <QtCore/QDebug> #include <QtQml/QQmlEngine> #include <QtQml/QQmlComponent> #include <QtCore/private/qabstractanimation_p.h> -#include <QtQml/private/qqmlinspectorservice_p.h> +#include <QtQml/private/qqmldebugconnector_p.h> #include <QtQml/private/qqmlcontext_p.h> #include <QtGui/QMouseEvent> @@ -61,6 +60,8 @@ // clearCache: void // Response for "destroyObject" carries the <debugId_int> of the destroyed object. +QT_BEGIN_NAMESPACE + const char REQUEST[] = "request"; const char RESPONSE[] = "response"; const char EVENT[] = "event"; @@ -78,10 +79,10 @@ const char CLEAR_CACHE[] = "clearCache"; namespace QmlJSDebugger { -AbstractViewInspector::AbstractViewInspector(QObject *parent) : +AbstractViewInspector::AbstractViewInspector(QQmlDebugService *service, QObject *parent) : QObject(parent), m_enabled(false), - m_debugService(QQmlInspectorService::instance()), + m_debugService(service), m_eventId(0), m_reloadEventId(-1) { @@ -260,14 +261,13 @@ void AbstractViewInspector::onQmlObjectDestroyed(QObject *object) return; QPair<int, int> ids = m_hashObjectsTobeDestroyed.take(object); - QQmlDebugService::removeInvalidObjectsFromHash(); QByteArray response; QQmlDebugStream rs(&response, QIODevice::WriteOnly); rs << QByteArray(RESPONSE) << ids.first << true << ids.second; - m_debugService->sendMessage(response); + emit m_debugService->messageToClient(m_debugService->name(), response); } void AbstractViewInspector::handleMessage(const QByteArray &message) @@ -361,7 +361,7 @@ void AbstractViewInspector::handleMessage(const QByteArray &message) QByteArray response; QQmlDebugStream rs(&response, QIODevice::WriteOnly); rs << QByteArray(RESPONSE) << requestId << success; - m_debugService->sendMessage(response); + emit m_debugService->messageToClient(m_debugService->name(), response); } void AbstractViewInspector::sendCurrentObjects(const QList<QObject*> &objects) @@ -372,11 +372,12 @@ void AbstractViewInspector::sendCurrentObjects(const QList<QObject*> &objects) ds << QByteArray(EVENT) << m_eventId++ << QByteArray(SELECT); QList<int> debugIds; + debugIds.reserve(objects.count()); foreach (QObject *object, objects) debugIds << QQmlDebugService::idForObject(object); ds << debugIds; - m_debugService->sendMessage(message); + emit m_debugService->messageToClient(m_debugService->name(), message); } void AbstractViewInspector::sendQmlFileReloaded(bool success) @@ -389,7 +390,7 @@ void AbstractViewInspector::sendQmlFileReloaded(bool success) QQmlDebugStream rs(&response, QIODevice::WriteOnly); rs << QByteArray(RESPONSE) << m_reloadEventId << success; - m_debugService->sendMessage(response); + emit m_debugService->messageToClient(m_debugService->name(), response); } QString AbstractViewInspector::idStringForObject(QObject *obj) const @@ -414,3 +415,5 @@ void AbstractViewInspector::removeTool(AbstractTool *tool) } } // namespace QmlJSDebugger + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/shared/abstractviewinspector.h b/src/plugins/qmltooling/qmldbg_inspector/abstractviewinspector.h index 02b4247bec..8f7ad4ac5b 100644 --- a/src/plugins/qmltooling/shared/abstractviewinspector.h +++ b/src/plugins/qmltooling/qmldbg_inspector/abstractviewinspector.h @@ -38,18 +38,14 @@ #include <QtCore/QObject> #include <QtCore/QStringList> -#include "qmlinspectorconstants.h" - QT_BEGIN_NAMESPACE class QQmlEngine; -class QQmlInspectorService; +class QQmlDebugService; class QKeyEvent; class QMouseEvent; class QWheelEvent; class QTouchEvent; -QT_END_NAMESPACE - namespace QmlJSDebugger { class AbstractTool; @@ -62,7 +58,7 @@ class AbstractViewInspector : public QObject Q_OBJECT public: - explicit AbstractViewInspector(QObject *parent = 0); + explicit AbstractViewInspector(QQmlDebugService *service, QObject *parent = 0); void handleMessage(const QByteArray &message); @@ -115,7 +111,7 @@ private: bool m_enabled; - QQmlInspectorService *m_debugService; + QQmlDebugService *m_debugService; QList<AbstractTool *> m_tools; int m_eventId; int m_reloadEventId; @@ -125,4 +121,6 @@ private: } // namespace QmlJSDebugger +QT_END_NAMESPACE + #endif // ABSTRACTVIEWINSPECTOR_H diff --git a/src/plugins/qmltooling/qmldbg_qtquick2/highlight.cpp b/src/plugins/qmltooling/qmldbg_inspector/highlight.cpp index 5af7a225fd..4d4e3aa720 100644 --- a/src/plugins/qmltooling/qmldbg_qtquick2/highlight.cpp +++ b/src/plugins/qmltooling/qmldbg_inspector/highlight.cpp @@ -38,8 +38,9 @@ #include <QtGui/QStaticText> #include <QtQuick/QQuickWindow> +QT_BEGIN_NAMESPACE + namespace QmlJSDebugger { -namespace QtQuick2 { Highlight::Highlight(QQuickItem *parent) : QQuickPaintedItem(parent) { @@ -193,5 +194,6 @@ void SelectionHighlight::disableNameDisplay() update(); } -} // namespace QtQuick2 } // namespace QmlJSDebugger + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_qtquick2/highlight.h b/src/plugins/qmltooling/qmldbg_inspector/highlight.h index 29d2f0d911..05f6382353 100644 --- a/src/plugins/qmltooling/qmldbg_qtquick2/highlight.h +++ b/src/plugins/qmltooling/qmldbg_inspector/highlight.h @@ -39,9 +39,9 @@ #include <QtGui/QTransform> #include <QtQuick/QQuickPaintedItem> +QT_BEGIN_NAMESPACE namespace QmlJSDebugger { -namespace QtQuick2 { class Highlight : public QQuickPaintedItem { @@ -104,7 +104,8 @@ public: void paint(QPainter *painter); }; -} // namespace QtQuick2 } // namespace QmlJSDebugger +QT_END_NAMESPACE + #endif // HIGHLIGHT_H diff --git a/src/plugins/qmltooling/qmldbg_qtquick2/inspecttool.cpp b/src/plugins/qmltooling/qmldbg_inspector/inspecttool.cpp index f86225fa0a..cc6b4ffb8c 100644 --- a/src/plugins/qmltooling/qmldbg_qtquick2/inspecttool.cpp +++ b/src/plugins/qmltooling/qmldbg_inspector/inspecttool.cpp @@ -48,8 +48,12 @@ #include <QtQuick/QQuickView> #include <QtQuick/QQuickItem> +QT_BEGIN_NAMESPACE + namespace QmlJSDebugger { -namespace QtQuick2 { + +static const double ZoomSnapDelta = 0.04; +static const int PressAndHoldTimeout = 800; InspectTool::InspectTool(QQuickViewInspector *inspector, QQuickView *view) : AbstractTool(inspector), @@ -60,7 +64,7 @@ InspectTool::InspectTool(QQuickViewInspector *inspector, QQuickView *view) : m_tapEvent(false), m_contentItem(view->contentItem()), m_originalPosition(view->contentItem()->position()), - m_smoothScaleFactor(Constants::ZoomSnapDelta), + m_smoothScaleFactor(ZoomSnapDelta), m_minScale(0.125f), m_maxScale(48.0f), m_originalScale(view->contentItem()->scale()), @@ -71,7 +75,7 @@ InspectTool::InspectTool(QQuickViewInspector *inspector, QQuickView *view) : { //Press and Hold Timer m_pressAndHoldTimer.setSingleShot(true); - m_pressAndHoldTimer.setInterval(Constants::PressAndHoldTimeout); + m_pressAndHoldTimer.setInterval(PressAndHoldTimeout); connect(&m_pressAndHoldTimer, SIGNAL(timeout()), SLOT(zoomTo100())); //Timer to display selected item's name m_nameDisplayTimer.setSingleShot(true); @@ -418,5 +422,6 @@ void InspectTool::showSelectedItemName() inspector()->showSelectedItemName(m_lastItem, m_mousePosition); } -} // namespace QtQuick2 } // namespace QmlJSDebugger + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_qtquick2/inspecttool.h b/src/plugins/qmltooling/qmldbg_inspector/inspecttool.h index 0b1b49fd93..fdb763d4b3 100644 --- a/src/plugins/qmltooling/qmldbg_qtquick2/inspecttool.h +++ b/src/plugins/qmltooling/qmldbg_inspector/inspecttool.h @@ -40,11 +40,12 @@ #include <QtCore/QPointer> #include <QtCore/QTimer> -QT_FORWARD_DECLARE_CLASS(QQuickView) -QT_FORWARD_DECLARE_CLASS(QQuickItem) +QT_BEGIN_NAMESPACE + +class QQuickView; +class QQuickItem; namespace QmlJSDebugger { -namespace QtQuick2 { class QQuickViewInspector; class HoverHighlight; @@ -119,7 +120,8 @@ private: QQuickItem *m_lastClickedItem; }; -} // namespace QtQuick2 } // namespace QmlJSDebugger +QT_END_NAMESPACE + #endif // INSPECTTOOL_H diff --git a/src/plugins/qmltooling/qmldbg_inspector/qmldbg_inspector.pro b/src/plugins/qmltooling/qmldbg_inspector/qmldbg_inspector.pro new file mode 100644 index 0000000000..1c3e5f387b --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_inspector/qmldbg_inspector.pro @@ -0,0 +1,27 @@ +TARGET = qmldbg_inspector +QT += qml-private quick-private core-private gui-private + +PLUGIN_TYPE = qmltooling +PLUGIN_CLASS_NAME = QQmlInspectorServiceFactory +load(qt_plugin) + +INCLUDEPATH *= $$PWD $$PWD/../shared + +SOURCES += \ + $$PWD/highlight.cpp \ + $$PWD/qquickviewinspector.cpp \ + $$PWD/abstracttool.cpp \ + $$PWD/abstractviewinspector.cpp \ + $$PWD/inspecttool.cpp \ + $$PWD/qqmlinspectorservice.cpp + +HEADERS += \ + $$PWD/highlight.h \ + $$PWD/qquickviewinspector.h \ + $$PWD/qqmlinspectorservicefactory.h \ + $$PWD/abstracttool.h \ + $$PWD/abstractviewinspector.h \ + $$PWD/inspecttool.h + +OTHER_FILES += \ + qqmlinspectorservice.json diff --git a/src/plugins/qmltooling/qmldbg_inspector/qqmlinspectorservice.cpp b/src/plugins/qmltooling/qmldbg_inspector/qqmlinspectorservice.cpp new file mode 100644 index 0000000000..1707091df3 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_inspector/qqmlinspectorservice.cpp @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlinspectorservicefactory.h" +#include "qquickviewinspector.h" + +#include <private/qqmlglobal_p.h> + +#include <QtCore/QCoreApplication> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QPluginLoader> + +QT_BEGIN_NAMESPACE + +class QQmlInspectorServiceImpl : public QQmlInspectorService +{ + Q_OBJECT + +public: + QQmlInspectorServiceImpl(QObject *parent = 0); + + void addView(QObject *); + void removeView(QObject *); + +protected: + virtual void stateChanged(State state); + virtual void messageReceived(const QByteArray &); + +private Q_SLOTS: + void processMessage(const QByteArray &message); + void updateState(); + +private: + friend class QQmlInspectorServiceFactory; + + QList<QObject*> m_views; + QmlJSDebugger::AbstractViewInspector *m_currentInspector; +}; + +QQmlInspectorServiceImpl::QQmlInspectorServiceImpl(QObject *parent): + QQmlInspectorService(1, parent), m_currentInspector(0) +{ +} + +void QQmlInspectorServiceImpl::addView(QObject *view) +{ + m_views.append(view); + updateState(); +} + +void QQmlInspectorServiceImpl::removeView(QObject *view) +{ + m_views.removeAll(view); + updateState(); +} + +void QQmlInspectorServiceImpl::stateChanged(State /*state*/) +{ + QMetaObject::invokeMethod(this, "updateState", Qt::QueuedConnection); +} + +void QQmlInspectorServiceImpl::updateState() +{ + delete m_currentInspector; + m_currentInspector = 0; + + if (m_views.isEmpty() || state() != Enabled) + return; + + QQuickView *qtQuickView = qobject_cast<QQuickView*>(m_views.first()); + if (qtQuickView) + m_currentInspector = new QmlJSDebugger::QQuickViewInspector(this, qtQuickView, this); + else + qWarning() << "QQmlInspector: No inspector available for view '" + << m_views.first()->metaObject()->className() << "'."; +} + +void QQmlInspectorServiceImpl::messageReceived(const QByteArray &message) +{ + QMetaObject::invokeMethod(this, "processMessage", Qt::QueuedConnection, Q_ARG(QByteArray, message)); +} + +void QQmlInspectorServiceImpl::processMessage(const QByteArray &message) +{ + if (m_currentInspector) + m_currentInspector->handleMessage(message); +} + +QQmlDebugService *QQmlInspectorServiceFactory::create(const QString &key) +{ + return key == QQmlInspectorServiceImpl::s_key ? new QQmlInspectorServiceImpl(this) : 0; +} + +QT_END_NAMESPACE + +#include "qqmlinspectorservice.moc" diff --git a/src/plugins/qmltooling/qmldbg_inspector/qqmlinspectorservice.json b/src/plugins/qmltooling/qmldbg_inspector/qqmlinspectorservice.json new file mode 100644 index 0000000000..9ace8dad2f --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_inspector/qqmlinspectorservice.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "QmlInspector" ] +} diff --git a/src/plugins/qmltooling/qmldbg_inspector/qqmlinspectorservicefactory.h b/src/plugins/qmltooling/qmldbg_inspector/qqmlinspectorservicefactory.h new file mode 100644 index 0000000000..52f84a362d --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_inspector/qqmlinspectorservicefactory.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $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 <private/qqmldebugserviceinterfaces_p.h> +#include <private/qqmldebugservicefactory_p.h> + +#include <QtQml/qtqmlglobal.h> +#include <QtCore/QList> + +QT_BEGIN_NAMESPACE + +class QQmlInspectorServiceFactory : public QQmlDebugServiceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlDebugServiceFactory_iid FILE "qqmlinspectorservice.json") +public: + QQmlDebugService *create(const QString &key); +}; + +QT_END_NAMESPACE + +#endif // QQMLINSPECTORSERVICE_H diff --git a/src/plugins/qmltooling/qmldbg_qtquick2/qquickviewinspector.cpp b/src/plugins/qmltooling/qmldbg_inspector/qquickviewinspector.cpp index e61c421bfa..de9d5617b5 100644 --- a/src/plugins/qmltooling/qmldbg_qtquick2/qquickviewinspector.cpp +++ b/src/plugins/qmltooling/qmldbg_inspector/qquickviewinspector.cpp @@ -37,7 +37,6 @@ #include "inspecttool.h" #include <QtQml/private/qqmlengine_p.h> -#include <QtQml/private/qqmldebugservice_p.h> #include <QtQuick/private/qquickitem_p.h> #include <QtQuick/QQuickView> @@ -45,8 +44,8 @@ #include <cfloat> +QT_BEGIN_NAMESPACE namespace QmlJSDebugger { -namespace QtQuick2 { /* * Collects all the items at the given position, from top to bottom. @@ -110,8 +109,9 @@ static QQuickItem *itemAt(QQuickItem *item, const QPointF &pos, } -QQuickViewInspector::QQuickViewInspector(QQuickView *view, QObject *parent) : - AbstractViewInspector(parent), +QQuickViewInspector::QQuickViewInspector(QQmlDebugService *service, QQuickView *view, + QObject *parent) : + AbstractViewInspector(service, parent), m_view(view), m_overlay(new QQuickItem), m_inspectTool(new InspectTool(this, view)), @@ -211,6 +211,7 @@ void QQuickViewInspector::setSelectedItems(const QList<QQuickItem *> &items) return; QList<QObject*> objectList; + objectList.reserve(items.count()); foreach (QQuickItem *item, items) objectList << item; @@ -314,8 +315,6 @@ void QQuickViewInspector::reloadQmlFile(const QHash<QString, QByteArray> &change // Reset the selection since we are reloading the main qml setSelectedItems(QList<QQuickItem *>()); - QQmlDebugService::clearObjectsFromHash(); - QHash<QUrl, QByteArray> debugCache; foreach (const QString &str, changesHash.keys()) @@ -373,5 +372,6 @@ void QQuickViewInspector::applyAppOnTop() setWindowFlags(flags); } -} // namespace QtQuick2 } // namespace QmlJSDebugger + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_qtquick2/qquickviewinspector.h b/src/plugins/qmltooling/qmldbg_inspector/qquickviewinspector.h index be3ede4d07..e823e5a03d 100644 --- a/src/plugins/qmltooling/qmldbg_qtquick2/qquickviewinspector.h +++ b/src/plugins/qmltooling/qmldbg_inspector/qquickviewinspector.h @@ -43,10 +43,8 @@ QT_BEGIN_NAMESPACE class QQuickView; class QQuickItem; -QT_END_NAMESPACE namespace QmlJSDebugger { -namespace QtQuick2 { class InspectTool; class SelectionHighlight; @@ -55,7 +53,7 @@ class QQuickViewInspector : public AbstractViewInspector { Q_OBJECT public: - explicit QQuickViewInspector(QQuickView *view, QObject *parent = 0); + explicit QQuickViewInspector(QQmlDebugService *service, QQuickView *view, QObject *parent = 0); // AbstractViewInspector void changeCurrentObjects(const QList<QObject*> &objects); @@ -104,7 +102,8 @@ private: bool m_appOnTop; }; -} // namespace QtQuick2 } // namespace QmlJSDebugger +QT_END_NAMESPACE + #endif // QQUICKVIEWINSPECTOR_H diff --git a/src/plugins/qmltooling/qmldbg_local/qlocalclientconnection.cpp b/src/plugins/qmltooling/qmldbg_local/qlocalclientconnection.cpp new file mode 100644 index 0000000000..057bf9523e --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_local/qlocalclientconnection.cpp @@ -0,0 +1,157 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalclientconnectionfactory.h" +#include "qpacketprotocol.h" +#include "qqmldebugserver.h" + +#include <QtCore/qplugin.h> +#include <QtNetwork/qlocalsocket.h> + +QT_BEGIN_NAMESPACE + + +class QLocalClientConnection : public QQmlDebugServerConnection +{ + Q_OBJECT + Q_DISABLE_COPY(QLocalClientConnection) + +public: + QLocalClientConnection(); + ~QLocalClientConnection(); + + void setServer(QQmlDebugServer *server); + bool setPortRange(int portFrom, int portTo, bool block, const QString &hostaddress); + bool setFileName(const QString &filename, bool block); + + bool isConnected() const; + void disconnect(); + + void waitForConnection(); + void flush(); + +private slots: + void connectionEstablished(); + +private: + bool connectToServer(); + + bool m_block; + QString m_filename; + QLocalSocket *m_socket; + QQmlDebugServer *m_debugServer; +}; + +QLocalClientConnection::QLocalClientConnection() : + m_block(false), + m_socket(0), + m_debugServer(0) +{ +} + +QLocalClientConnection::~QLocalClientConnection() +{ + if (isConnected()) + disconnect(); +} + +void QLocalClientConnection::setServer(QQmlDebugServer *server) +{ + m_debugServer = server; +} + +bool QLocalClientConnection::isConnected() const +{ + return m_socket && m_socket->state() == QLocalSocket::ConnectedState; +} + +void QLocalClientConnection::disconnect() +{ + while (m_socket && m_socket->bytesToWrite() > 0) + m_socket->waitForBytesWritten(); + + m_socket->deleteLater(); + m_socket = 0; +} + +bool QLocalClientConnection::setPortRange(int portFrom, int portTo, bool block, + const QString &hostaddress) +{ + Q_UNUSED(portFrom); + Q_UNUSED(portTo); + Q_UNUSED(block); + Q_UNUSED(hostaddress); + return false; +} + +bool QLocalClientConnection::setFileName(const QString &filename, bool block) +{ + m_filename = filename; + m_block = block; + return connectToServer(); +} + +void QLocalClientConnection::waitForConnection() +{ + m_socket->waitForConnected(-1); +} + +bool QLocalClientConnection::connectToServer() +{ + m_socket = new QLocalSocket; + m_socket->setParent(this); + QObject::connect(m_socket, SIGNAL(connected()), this, SLOT(connectionEstablished())); + m_socket->connectToServer(m_filename); + qDebug("QML Debugger: Connecting to socket %s...", m_filename.toLatin1().constData()); + return true; +} + +void QLocalClientConnection::flush() +{ + if (m_socket) + m_socket->flush(); +} + +void QLocalClientConnection::connectionEstablished() +{ + m_debugServer->setDevice(m_socket); +} + +QQmlDebugServerConnection *QLocalClientConnectionFactory::create(const QString &key) +{ + return (key == QLatin1String("QLocalClientConnection") ? new QLocalClientConnection : 0); +} + +QT_END_NAMESPACE + +#include "qlocalclientconnection.moc" diff --git a/src/plugins/qmltooling/qmldbg_local/qlocalclientconnection.json b/src/plugins/qmltooling/qmldbg_local/qlocalclientconnection.json new file mode 100644 index 0000000000..5f8fd49296 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_local/qlocalclientconnection.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "QLocalClientConnection" ] +} diff --git a/src/plugins/qmltooling/qmldbg_local/qlocalclientconnectionfactory.h b/src/plugins/qmltooling/qmldbg_local/qlocalclientconnectionfactory.h new file mode 100644 index 0000000000..110e0c2395 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_local/qlocalclientconnectionfactory.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCALCLIENTCONNECTIONFACTORY_H +#define QLOCALCLIENTCONNECTIONFACTORY_H + +#include "qqmldebugserverconnection.h" + +QT_BEGIN_NAMESPACE + +class QLocalClientConnectionFactory : public QQmlDebugServerConnectionFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlDebugServerConnectionFactory_iid FILE "qlocalclientconnection.json") + Q_INTERFACES(QQmlDebugServerConnectionFactory) +public: + QQmlDebugServerConnection *create(const QString &key); +}; + +QT_END_NAMESPACE + +#endif // QLOCALCLIENTCONNECTIONFACTORY_H diff --git a/src/plugins/qmltooling/qmldbg_local/qmldbg_local.pro b/src/plugins/qmltooling/qmldbg_local/qmldbg_local.pro new file mode 100644 index 0000000000..491be04b15 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_local/qmldbg_local.pro @@ -0,0 +1,20 @@ +TARGET = qmldbg_local +QT = qml-private + +PLUGIN_TYPE = qmltooling +PLUGIN_CLASS_NAME = QLocalClientConnectionFactory +load(qt_plugin) + +SOURCES += \ + $$PWD/qlocalclientconnection.cpp + +HEADERS += \ + $$PWD/qlocalclientconnectionfactory.h \ + $$PWD/../shared/qqmldebugserver.h \ + $$PWD/../shared/qqmldebugserverconnection.h + +INCLUDEPATH += $$PWD \ + $$PWD/../shared + +OTHER_FILES += \ + $$PWD/qlocalclientconnection.json diff --git a/src/plugins/qmltooling/qmldbg_native/qmldbg_native.pro b/src/plugins/qmltooling/qmldbg_native/qmldbg_native.pro new file mode 100644 index 0000000000..7dc16b8c44 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_native/qmldbg_native.pro @@ -0,0 +1,12 @@ +TARGET = qmldbg_native +QT += qml-private core-private + +PLUGIN_TYPE = qmltooling +PLUGIN_CLASS_NAME = QQmlNativeDebugConnectorFactory +load(qt_plugin) + +SOURCES += \ + $$PWD/qqmlnativedebugconnector.cpp + +OTHER_FILES += \ + $$PWD/qqmlnativedebugconnector.json diff --git a/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.cpp b/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.cpp new file mode 100644 index 0000000000..6621eafb27 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.cpp @@ -0,0 +1,383 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qqmldebugconnector_p.h> +#include <private/qhooks_p.h> + +#include <qqmlengine.h> + +#include <QtCore/qdebug.h> +#include <QtCore/qjsonarray.h> +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qjsonvalue.h> +#include <QtCore/qpointer.h> +#include <QtCore/qvector.h> + +//#define TRACE_PROTOCOL(s) qDebug() << s +#define TRACE_PROTOCOL(s) + +QT_USE_NAMESPACE + +static bool expectSyncronousResponse = false; +Q_GLOBAL_STATIC(QByteArray, responseBuffer) + +extern "C" { + +Q_DECL_EXPORT const char *qt_qmlDebugMessageBuffer; +Q_DECL_EXPORT int qt_qmlDebugMessageLength; +Q_DECL_EXPORT bool qt_qmlDebugConnectionBlocker; + +// In blocking mode, this will busy wait until the debugger sets block to false. +Q_DECL_EXPORT void qt_qmlDebugConnectorOpen(); + +// First thing, set the debug stream version. Please use this function as we might move the version +// member to some other place. +Q_DECL_EXPORT void qt_qmlDebugSetStreamVersion(int version) +{ + QQmlDebugStream::s_dataStreamVersion = version; +} + + +// Break in this one to process output from an asynchronous message/ +Q_DECL_EXPORT void qt_qmlDebugMessageAvailable() +{ +} + + +// Break in this one to get notified about construction and destruction of +// interesting objects, such as QmlEngines. +Q_DECL_EXPORT void qt_qmlDebugObjectAvailable() +{ +} + +Q_DECL_EXPORT void qt_qmlDebugClearBuffer() +{ + responseBuffer->clear(); +} + +// Send a message to a service. +Q_DECL_EXPORT bool qt_qmlDebugSendDataToService(const char *serviceName, const char *hexData) +{ + QByteArray msg = QByteArray::fromHex(hexData); + + QQmlDebugConnector *instance = QQmlDebugConnector::instance(); + if (!instance) + return false; + + QQmlDebugService *recipient = instance->service(serviceName); + if (!recipient) + return false; + + TRACE_PROTOCOL("Recipient: " << recipient << " got message: " << msg); + expectSyncronousResponse = true; + recipient->messageReceived(msg); + expectSyncronousResponse = false; + + return true; +} + +// Enable a service. +Q_DECL_EXPORT bool qt_qmlDebugEnableService(const char *data) +{ + QQmlDebugConnector *instance = QQmlDebugConnector::instance(); + if (!instance) + return false; + + QString name = QString::fromLatin1(data); + QQmlDebugService *service = instance->service(name); + if (!service || service->state() == QQmlDebugService::Enabled) + return false; + + service->stateAboutToBeChanged(QQmlDebugService::Enabled); + service->setState(QQmlDebugService::Enabled); + service->stateChanged(QQmlDebugService::Enabled); + return true; +} + +Q_DECL_EXPORT bool qt_qmlDebugDisableService(const char *data) +{ + QQmlDebugConnector *instance = QQmlDebugConnector::instance(); + if (!instance) + return false; + + QString name = QString::fromLatin1(data); + QQmlDebugService *service = instance->service(name); + if (!service || service->state() == QQmlDebugService::Unavailable) + return false; + + service->stateAboutToBeChanged(QQmlDebugService::Unavailable); + service->setState(QQmlDebugService::Unavailable); + service->stateChanged(QQmlDebugService::Unavailable); + return true; +} + +quintptr qt_qmlDebugTestHooks[] = { + quintptr(1), // Internal Version + quintptr(6), // Number of entries following + quintptr(&qt_qmlDebugMessageBuffer), + quintptr(&qt_qmlDebugMessageLength), + quintptr(&qt_qmlDebugSendDataToService), + quintptr(&qt_qmlDebugEnableService), + quintptr(&qt_qmlDebugDisableService), + quintptr(&qt_qmlDebugObjectAvailable) +}; + +// In blocking mode, this will busy wait until the debugger sets block to false. +Q_DECL_EXPORT void qt_qmlDebugConnectorOpen() +{ + TRACE_PROTOCOL("Opening native debug connector"); + + // FIXME: Use a dedicated hook. Startup is a safe workaround, though, + // as we are already beyond its only use. + qtHookData[QHooks::Startup] = quintptr(&qt_qmlDebugTestHooks); + + while (qt_qmlDebugConnectionBlocker) + ; + + TRACE_PROTOCOL("Opened native debug connector"); +} + +} // extern "C" + +QT_BEGIN_NAMESPACE + +class QQmlNativeDebugConnector : public QQmlDebugConnector +{ + Q_OBJECT + +public: + QQmlNativeDebugConnector(); + ~QQmlNativeDebugConnector(); + + bool blockingMode() const; + QQmlDebugService *service(const QString &name) const; + void addEngine(QQmlEngine *engine); + void removeEngine(QQmlEngine *engine); + bool addService(const QString &name, QQmlDebugService *service); + bool removeService(const QString &name); + bool open(const QVariantHash &configuration); + +private slots: + void sendMessage(const QString &name, const QByteArray &message); + void sendMessages(const QString &name, const QList<QByteArray> &messages); + +private: + void announceObjectAvailability(const QString &objectType, QObject *object, bool available); + + QVector<QQmlDebugService *> m_services; + bool m_blockingMode; +}; + +QQmlNativeDebugConnector::QQmlNativeDebugConnector() + : m_blockingMode(false) +{ + const QString args = commandLineArguments(); + const QStringList lstjsDebugArguments = args.split(QLatin1Char(',')); + QStringList services; + QStringList::const_iterator argsItEnd = lstjsDebugArguments.cend(); + QStringList::const_iterator argsIt = lstjsDebugArguments.cbegin(); + for (; argsIt != argsItEnd; ++argsIt) { + const QString strArgument = *argsIt; + if (strArgument == QLatin1String("block")) { + m_blockingMode = true; + } else if (strArgument == QLatin1String("native")) { + // Ignore. This is used to signal that this connector + // should be loaded and that has already happened. + } else if (strArgument.startsWith(QLatin1String("services:"))) { + services.append(strArgument.mid(9)); + } else if (!services.isEmpty()) { + services.append(strArgument); + } else { + qWarning("QML Debugger: Invalid argument \"%s\" detected. Ignoring the same.", + qUtf8Printable(strArgument)); + } + } + setServices(services); +} + +QQmlNativeDebugConnector::~QQmlNativeDebugConnector() +{ + foreach (QQmlDebugService *service, m_services) { + service->stateAboutToBeChanged(QQmlDebugService::NotConnected); + service->setState(QQmlDebugService::NotConnected); + service->stateChanged(QQmlDebugService::NotConnected); + } +} + +bool QQmlNativeDebugConnector::blockingMode() const +{ + return m_blockingMode; +} + +QQmlDebugService *QQmlNativeDebugConnector::service(const QString &name) const +{ + for (QVector<QQmlDebugService *>::ConstIterator i = m_services.begin(); i != m_services.end(); + ++i) { + if ((*i)->name() == name) + return *i; + } + return 0; +} + +void QQmlNativeDebugConnector::addEngine(QQmlEngine *engine) +{ + TRACE_PROTOCOL("Add engine to connector:" << engine); + foreach (QQmlDebugService *service, m_services) + service->engineAboutToBeAdded(engine); + + announceObjectAvailability(QLatin1String("qmlengine"), engine, true); + + foreach (QQmlDebugService *service, m_services) + service->engineAdded(engine); +} + +void QQmlNativeDebugConnector::removeEngine(QQmlEngine *engine) +{ + TRACE_PROTOCOL("Remove engine from connector:" << engine); + foreach (QQmlDebugService *service, m_services) + service->engineAboutToBeRemoved(engine); + + announceObjectAvailability(QLatin1String("qmlengine"), engine, false); + + foreach (QQmlDebugService *service, m_services) + service->engineRemoved(engine); +} + +void QQmlNativeDebugConnector::announceObjectAvailability(const QString &objectType, + QObject *object, bool available) +{ + QJsonObject ob; + ob.insert(QLatin1String("objecttype"), objectType); + ob.insert(QLatin1String("object"), QString::number(quintptr(object))); + ob.insert(QLatin1String("available"), available); + QJsonDocument doc; + doc.setObject(ob); + + QByteArray ba = doc.toJson(QJsonDocument::Compact); + qt_qmlDebugMessageBuffer = ba.constData(); + qt_qmlDebugMessageLength = ba.size(); + TRACE_PROTOCOL("Reporting engine availabilty"); + qt_qmlDebugObjectAvailable(); // Trigger native breakpoint. +} + +bool QQmlNativeDebugConnector::addService(const QString &name, QQmlDebugService *service) +{ + TRACE_PROTOCOL("Add service to connector: " << qPrintable(name) << service); + for (QVector<QQmlDebugService *>::ConstIterator i = m_services.begin(); i != m_services.end(); + ++i) { + if ((*i)->name() == name) + return false; + } + + connect(service, &QQmlDebugService::messageToClient, + this, &QQmlNativeDebugConnector::sendMessage); + connect(service, &QQmlDebugService::messagesToClient, + this, &QQmlNativeDebugConnector::sendMessages); + + service->setState(QQmlDebugService::Unavailable); + + m_services << service; + return true; +} + +bool QQmlNativeDebugConnector::removeService(const QString &name) +{ + for (QVector<QQmlDebugService *>::Iterator i = m_services.begin(); i != m_services.end(); ++i) { + if ((*i)->name() == name) { + QQmlDebugService *service = *i; + m_services.erase(i); + service->setState(QQmlDebugService::NotConnected); + + disconnect(service, &QQmlDebugService::messagesToClient, + this, &QQmlNativeDebugConnector::sendMessages); + disconnect(service, &QQmlDebugService::messageToClient, + this, &QQmlNativeDebugConnector::sendMessage); + + return true; + } + } + return false; +} + +bool QQmlNativeDebugConnector::open(const QVariantHash &configuration) +{ + m_blockingMode = configuration.value(QStringLiteral("block"), m_blockingMode).toBool(); + qt_qmlDebugConnectionBlocker = m_blockingMode; + qt_qmlDebugConnectorOpen(); + return true; +} + +void QQmlNativeDebugConnector::sendMessage(const QString &name, const QByteArray &message) +{ + (*responseBuffer) += name.toUtf8() + ' ' + QByteArray::number(message.size()) + ' ' + message; + qt_qmlDebugMessageBuffer = responseBuffer->constData(); + qt_qmlDebugMessageLength = responseBuffer->size(); + // Responses are allowed to accumulate, the buffer will be cleared by + // separate calls to qt_qmlDebugClearBuffer() once the synchronous + // function return ('if' branch below) or in the native breakpoint handler + // ('else' branch below). + if (expectSyncronousResponse) { + TRACE_PROTOCOL("Expected synchronous response in " << message); + // Do not trigger the native breakpoint on qt_qmlDebugMessageFromService. + } else { + TRACE_PROTOCOL("Found asynchronous message in " << message); + // Trigger native breakpoint. + qt_qmlDebugMessageAvailable(); + } +} + +void QQmlNativeDebugConnector::sendMessages(const QString &name, const QList<QByteArray> &messages) +{ + for (int i = 0; i != messages.size(); ++i) + sendMessage(name, messages.at(i)); +} + +class QQmlNativeDebugConnectorFactory : public QQmlDebugConnectorFactory +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID QQmlDebugConnectorFactory_iid FILE "qqmlnativedebugconnector.json") + +public: + QQmlNativeDebugConnectorFactory() {} + + QQmlDebugConnector *create(const QString &key) + { + return key == QLatin1String("QQmlNativeDebugConnector") ? new QQmlNativeDebugConnector : 0; + } +}; + +QT_END_NAMESPACE + +#include "qqmlnativedebugconnector.moc" diff --git a/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.json b/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.json new file mode 100644 index 0000000000..925e6a665c --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "QQmlNativeDebugConnector" ] +} diff --git a/src/plugins/qmltooling/qmldbg_profiler/qmldbg_profiler.pro b/src/plugins/qmltooling/qmldbg_profiler/qmldbg_profiler.pro new file mode 100644 index 0000000000..e1c4095d88 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_profiler/qmldbg_profiler.pro @@ -0,0 +1,28 @@ +TARGET = qmldbg_profiler +QT = qml-private core-private + +PLUGIN_TYPE = qmltooling +PLUGIN_CLASS_NAME = QQmlProfilerServiceFactory +load(qt_plugin) + +SOURCES += \ + $$PWD/qqmlenginecontrolservice.cpp \ + $$PWD/qqmlprofileradapter.cpp \ + $$PWD/qqmlprofilerservice.cpp \ + $$PWD/qqmlprofilerservicefactory.cpp \ + $$PWD/qv4profileradapter.cpp + +HEADERS += \ + $$PWD/../shared/qqmlconfigurabledebugservice.h \ + $$PWD/qqmlenginecontrolservice.h \ + $$PWD/qqmlprofileradapter.h \ + $$PWD/qqmlprofilerservice.h \ + $$PWD/qqmlprofilerservicefactory.h \ + $$PWD/qv4profileradapter.h + +INCLUDEPATH += $$PWD \ + $$PWD/../shared + +OTHER_FILES += \ + $$PWD/qqmlprofilerservice.json + diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlenginecontrolservice.cpp b/src/plugins/qmltooling/qmldbg_profiler/qqmlenginecontrolservice.cpp new file mode 100644 index 0000000000..4f131ac481 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlenginecontrolservice.cpp @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlenginecontrolservice.h" +#include <QQmlEngine> + +QT_BEGIN_NAMESPACE + +const QString QQmlEngineControlService::s_key = QStringLiteral("EngineControl"); + +QQmlEngineControlService::QQmlEngineControlService(QObject *parent) : + QQmlDebugService(s_key, 1, parent) +{ +} + +void QQmlEngineControlService::messageReceived(const QByteArray &message) +{ + QMutexLocker lock(&dataMutex); + QQmlDebugStream d(message); + int command; + int engineId; + d >> command >> engineId; + QQmlEngine *engine = qobject_cast<QQmlEngine *>(objectForId(engineId)); + if (command == StartWaitingEngine && startingEngines.contains(engine)) { + startingEngines.removeOne(engine); + emit attachedToEngine(engine); + } else if (command == StopWaitingEngine && stoppingEngines.contains(engine)) { + stoppingEngines.removeOne(engine); + emit detachedFromEngine(engine); + } +} + +void QQmlEngineControlService::engineAboutToBeAdded(QQmlEngine *engine) +{ + QMutexLocker lock(&dataMutex); + if (state() == Enabled) { + Q_ASSERT(!stoppingEngines.contains(engine)); + Q_ASSERT(!startingEngines.contains(engine)); + startingEngines.append(engine); + sendMessage(EngineAboutToBeAdded, engine); + } else { + emit attachedToEngine(engine); + } +} + +void QQmlEngineControlService::engineAboutToBeRemoved(QQmlEngine *engine) +{ + QMutexLocker lock(&dataMutex); + if (state() == Enabled) { + Q_ASSERT(!stoppingEngines.contains(engine)); + Q_ASSERT(!startingEngines.contains(engine)); + stoppingEngines.append(engine); + sendMessage(EngineAboutToBeRemoved, engine); + } else { + emit detachedFromEngine(engine); + } +} + +void QQmlEngineControlService::engineAdded(QQmlEngine *engine) +{ + if (state() == Enabled) { + QMutexLocker lock(&dataMutex); + Q_ASSERT(!startingEngines.contains(engine)); + Q_ASSERT(!stoppingEngines.contains(engine)); + sendMessage(EngineAdded, engine); + } +} + +void QQmlEngineControlService::engineRemoved(QQmlEngine *engine) +{ + if (state() == Enabled) { + QMutexLocker lock(&dataMutex); + Q_ASSERT(!startingEngines.contains(engine)); + Q_ASSERT(!stoppingEngines.contains(engine)); + sendMessage(EngineRemoved, engine); + } +} + +void QQmlEngineControlService::sendMessage(QQmlEngineControlService::MessageType type, QQmlEngine *engine) +{ + QByteArray message; + QQmlDebugStream d(&message, QIODevice::WriteOnly); + d << type << idForObject(engine); + emit messageToClient(name(), message); +} + +void QQmlEngineControlService::stateChanged(State) +{ + // We flush everything for any kind of state change, to avoid complicated timing issues. + QMutexLocker lock(&dataMutex); + foreach (QQmlEngine *engine, startingEngines) + emit attachedToEngine(engine); + startingEngines.clear(); + foreach (QQmlEngine *engine, stoppingEngines) + emit detachedFromEngine(engine); + stoppingEngines.clear(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlenginecontrolservice.h b/src/plugins/qmltooling/qmldbg_profiler/qqmlenginecontrolservice.h new file mode 100644 index 0000000000..e2a93e562a --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlenginecontrolservice.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLENGINECONTROLSERVICE_H +#define QQMLENGINECONTROLSERVICE_H + +#include <QMutex> +#include <private/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. +// + +QT_BEGIN_NAMESPACE + +class QQmlEngineControlService : public QQmlDebugService +{ +public: + static const QString s_key; + + enum MessageType { + EngineAboutToBeAdded, + EngineAdded, + EngineAboutToBeRemoved, + EngineRemoved + }; + + enum CommandType { + StartWaitingEngine, + StopWaitingEngine + }; + + QQmlEngineControlService(QObject *parent = 0); + +protected: + QMutex dataMutex; + QList<QQmlEngine *> startingEngines; + QList<QQmlEngine *> stoppingEngines; + + void messageReceived(const QByteArray &); + void engineAboutToBeAdded(QQmlEngine *); + void engineAboutToBeRemoved(QQmlEngine *); + void engineAdded(QQmlEngine *); + void engineRemoved(QQmlEngine *); + + void sendMessage(MessageType type, QQmlEngine *engine); + + void stateChanged(State); +}; + +QT_END_NAMESPACE + +#endif // QQMLENGINECONTROLSERVICE_H diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.cpp b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.cpp new file mode 100644 index 0000000000..245900abae --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.cpp @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlprofileradapter.h" +#include <private/qqmldebugserviceinterfaces_p.h> + +QT_BEGIN_NAMESPACE + +QQmlProfilerAdapter::QQmlProfilerAdapter(QQmlProfilerService *service, QQmlEnginePrivate *engine) : + QQmlAbstractProfilerAdapter(service), next(0) +{ + engine->enableProfiler(); + connect(this, SIGNAL(profilingEnabled(quint64)), engine->profiler, SLOT(startProfiling(quint64))); + connect(this, SIGNAL(profilingEnabledWhileWaiting(quint64)), + engine->profiler, SLOT(startProfiling(quint64)), Qt::DirectConnection); + connect(this, SIGNAL(profilingDisabled()), engine->profiler, SLOT(stopProfiling())); + connect(this, SIGNAL(profilingDisabledWhileWaiting()), + engine->profiler, SLOT(stopProfiling()), Qt::DirectConnection); + connect(this, SIGNAL(dataRequested()), engine->profiler, SLOT(reportData())); + connect(this, SIGNAL(referenceTimeKnown(QElapsedTimer)), + engine->profiler, SLOT(setTimer(QElapsedTimer))); + connect(engine->profiler, SIGNAL(dataReady(QVector<QQmlProfilerData>)), + this, SLOT(receiveData(QVector<QQmlProfilerData>))); +} + +// convert to QByteArrays that can be sent to the debug client +// use of QDataStream can skew results +// (see tst_qqmldebugtrace::trace() benchmark) +static void qQmlProfilerDataToByteArrays(const QQmlProfilerData *d, QList<QByteArray> &messages) +{ + QByteArray data; + Q_ASSERT_X(((d->messageType | d->detailType) & (1 << 31)) == 0, Q_FUNC_INFO, + "You can use at most 31 message types and 31 detail types."); + for (uint decodedMessageType = 0; (d->messageType >> decodedMessageType) != 0; + ++decodedMessageType) { + if ((d->messageType & (1 << decodedMessageType)) == 0) + continue; + + for (uint decodedDetailType = 0; (d->detailType >> decodedDetailType) != 0; + ++decodedDetailType) { + if ((d->detailType & (1 << decodedDetailType)) == 0) + continue; + + //### using QDataStream is relatively expensive + QQmlDebugStream ds(&data, QIODevice::WriteOnly); + ds << d->time << decodedMessageType << decodedDetailType; + + switch (decodedMessageType) { + case QQmlProfilerDefinitions::RangeStart: + if (decodedDetailType == (int)QQmlProfilerDefinitions::Binding) + ds << QQmlProfilerDefinitions::QmlBinding; + break; + case QQmlProfilerDefinitions::RangeData: + ds << (d->detailString.isEmpty() ? d->detailUrl.toString() : d->detailString); + break; + case QQmlProfilerDefinitions::RangeLocation: + ds << (d->detailUrl.isEmpty() ? d->detailString : d->detailUrl.toString()) << d->x + << d->y; + break; + case QQmlProfilerDefinitions::RangeEnd: break; + default: + Q_ASSERT_X(false, Q_FUNC_INFO, "Invalid message type."); + break; + } + messages << data; + data.clear(); + } + } +} + +qint64 QQmlProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages) +{ + while (next != data.length()) { + if (data[next].time > until) + return data[next].time; + qQmlProfilerDataToByteArrays(&(data[next++]), messages); + } + + next = 0; + data.clear(); + return -1; +} + +void QQmlProfilerAdapter::receiveData(const QVector<QQmlProfilerData> &new_data) +{ + if (data.isEmpty()) + data = new_data; + else + data.append(new_data); + service->dataReady(this); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_qtquick2/qtquick2plugin.h b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.h index 6866e74aa1..eceb58ce3a 100644 --- a/src/plugins/qmltooling/qmldbg_qtquick2/qtquick2plugin.h +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.h @@ -31,40 +31,39 @@ ** ****************************************************************************/ -#ifndef QTQUICK2PLUGINPLUGIN_H -#define QTQUICK2PLUGINPLUGIN_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/QPointer> -#include <QtQml/private/qqmlinspectorinterface_p.h> +#ifndef QQMLPROFILERADAPTER_H +#define QQMLPROFILERADAPTER_H -namespace QmlJSDebugger { +#include <private/qqmlabstractprofileradapter_p.h> +#include <private/qqmlprofiler_p.h> -class AbstractViewInspector; +QT_BEGIN_NAMESPACE -namespace QtQuick2 { - -class QtQuick2Plugin : public QObject, public QQmlInspectorInterface -{ +class QQmlProfilerAdapter : public QQmlAbstractProfilerAdapter { Q_OBJECT - Q_DISABLE_COPY(QtQuick2Plugin) - Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlInspectorInterface") - Q_INTERFACES(QQmlInspectorInterface) - public: - QtQuick2Plugin(); - ~QtQuick2Plugin(); + QQmlProfilerAdapter(QQmlProfilerService *service, QQmlEnginePrivate *engine); + qint64 sendMessages(qint64 until, QList<QByteArray> &messages); - // QQmlInspectorInterface - bool canHandleView(QObject *view); - void activate(QObject *view); - void deactivate(); - void clientMessage(const QByteArray &message); +public slots: + void receiveData(const QVector<QQmlProfilerData> &new_data); private: - QPointer<AbstractViewInspector> m_inspector; + QVector<QQmlProfilerData> data; + int next; }; -} // namespace QtQuick2 -} // namespace QmlJSDebugger +QT_END_NAMESPACE -#endif // QTQUICK2PLUGINPLUGIN_H +#endif // QQMLPROFILERADAPTER_H diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp new file mode 100644 index 0000000000..2654cf662b --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp @@ -0,0 +1,426 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlprofilerservice.h" +#include "qv4profileradapter.h" +#include "qqmlprofileradapter.h" +#include "qqmlprofilerservicefactory.h" +#include <private/qqmlengine_p.h> + +#include <QtCore/qdatastream.h> +#include <QtCore/qurl.h> +#include <QtCore/qtimer.h> +#include <QtCore/qthread.h> +#include <QtCore/qcoreapplication.h> + +QT_BEGIN_NAMESPACE + +QQmlProfilerServiceImpl::QQmlProfilerServiceImpl(QObject *parent) : + QQmlConfigurableDebugService<QQmlProfilerService>(1, parent), + m_waitingForStop(false) +{ + m_timer.start(); +} + +QQmlProfilerServiceImpl::~QQmlProfilerServiceImpl() +{ + // No need to lock here. If any engine or global profiler is still trying to register at this + // point we have a nasty bug anyway. + qDeleteAll(m_engineProfilers); + qDeleteAll(m_globalProfilers); +} + +void QQmlProfilerServiceImpl::dataReady(QQmlAbstractProfilerAdapter *profiler) +{ + QMutexLocker lock(&m_configMutex); + bool dataComplete = true; + for (QMultiMap<qint64, QQmlAbstractProfilerAdapter *>::iterator i(m_startTimes.begin()); i != m_startTimes.end();) { + if (i.value() == profiler) { + m_startTimes.erase(i++); + } else { + if (i.key() == -1) + dataComplete = false; + ++i; + } + } + m_startTimes.insert(0, profiler); + if (dataComplete) { + QList<QQmlEngine *> enginesToRelease; + foreach (QQmlEngine *engine, m_stoppingEngines) { + foreach (QQmlAbstractProfilerAdapter *engineProfiler, m_engineProfilers.values(engine)) { + if (m_startTimes.values().contains(engineProfiler)) { + enginesToRelease.append(engine); + break; + } + } + } + sendMessages(); + foreach (QQmlEngine *engine, enginesToRelease) { + m_stoppingEngines.removeOne(engine); + emit detachedFromEngine(engine); + } + } +} + +void QQmlProfilerServiceImpl::engineAboutToBeAdded(QQmlEngine *engine) +{ + Q_ASSERT_X(QThread::currentThread() == engine->thread(), Q_FUNC_INFO, + "QML profilers have to be added from the engine thread"); + + QMutexLocker lock(&m_configMutex); + QQmlProfilerAdapter *qmlAdapter = new QQmlProfilerAdapter(this, QQmlEnginePrivate::get(engine)); + QV4ProfilerAdapter *v4Adapter = new QV4ProfilerAdapter(this, QV8Engine::getV4(engine->handle())); + addEngineProfiler(qmlAdapter, engine); + addEngineProfiler(v4Adapter, engine); + QQmlConfigurableDebugService<QQmlProfilerService>::engineAboutToBeAdded(engine); +} + +void QQmlProfilerServiceImpl::engineAdded(QQmlEngine *engine) +{ + Q_ASSERT_X(QThread::currentThread() == engine->thread(), Q_FUNC_INFO, + "QML profilers have to be added from the engine thread"); + + QMutexLocker lock(&m_configMutex); + foreach (QQmlAbstractProfilerAdapter *profiler, m_engineProfilers.values(engine)) + profiler->stopWaiting(); +} + +void QQmlProfilerServiceImpl::engineAboutToBeRemoved(QQmlEngine *engine) +{ + Q_ASSERT_X(QThread::currentThread() == engine->thread(), Q_FUNC_INFO, + "QML profilers have to be removed from the engine thread"); + + QMutexLocker lock(&m_configMutex); + bool isRunning = false; + foreach (QQmlAbstractProfilerAdapter *profiler, m_engineProfilers.values(engine)) { + if (profiler->isRunning()) + isRunning = true; + profiler->startWaiting(); + } + if (isRunning) { + m_stoppingEngines.append(engine); + stopProfiling(engine); + } else { + emit detachedFromEngine(engine); + } +} + +void QQmlProfilerServiceImpl::engineRemoved(QQmlEngine *engine) +{ + Q_ASSERT_X(QThread::currentThread() == engine->thread(), Q_FUNC_INFO, + "QML profilers have to be removed from the engine thread"); + + QMutexLocker lock(&m_configMutex); + foreach (QQmlAbstractProfilerAdapter *profiler, m_engineProfilers.values(engine)) { + removeProfilerFromStartTimes(profiler); + delete profiler; + } + m_engineProfilers.remove(engine); +} + +void QQmlProfilerServiceImpl::addEngineProfiler(QQmlAbstractProfilerAdapter *profiler, QQmlEngine *engine) +{ + profiler->moveToThread(thread()); + profiler->synchronize(m_timer); + m_engineProfilers.insert(engine, profiler); +} + +void QQmlProfilerServiceImpl::addGlobalProfiler(QQmlAbstractProfilerAdapter *profiler) +{ + QMutexLocker lock(&m_configMutex); + profiler->synchronize(m_timer); + m_globalProfilers.append(profiler); + // Global profiler, not connected to a specific engine. + // Global profilers are started whenever any engine profiler is started and stopped when + // all engine profilers are stopped. + quint64 features = 0; + foreach (QQmlAbstractProfilerAdapter *engineProfiler, m_engineProfilers) + features |= engineProfiler->features(); + + if (features != 0) + profiler->startProfiling(features); +} + +void QQmlProfilerServiceImpl::removeGlobalProfiler(QQmlAbstractProfilerAdapter *profiler) +{ + QMutexLocker lock(&m_configMutex); + removeProfilerFromStartTimes(profiler); + m_globalProfilers.removeOne(profiler); + delete profiler; +} + +void QQmlProfilerServiceImpl::removeProfilerFromStartTimes(const QQmlAbstractProfilerAdapter *profiler) +{ + for (QMultiMap<qint64, QQmlAbstractProfilerAdapter *>::iterator i(m_startTimes.begin()); + i != m_startTimes.end();) { + if (i.value() == profiler) { + m_startTimes.erase(i++); + break; + } else { + ++i; + } + } +} + +/*! + * Start profiling the given \a engine. If \a engine is 0, start all engine profilers that aren't + * currently running. + * + * If any engine profiler is started like that also start all global profilers. + */ +void QQmlProfilerServiceImpl::startProfiling(QQmlEngine *engine, quint64 features) +{ + QMutexLocker lock(&m_configMutex); + + QByteArray message; + QQmlDebugStream d(&message, QIODevice::WriteOnly); + + d << m_timer.nsecsElapsed() << (int)Event << (int)StartTrace; + bool startedAny = false; + if (engine != 0) { + foreach (QQmlAbstractProfilerAdapter *profiler, m_engineProfilers.values(engine)) { + if (!profiler->isRunning()) { + profiler->startProfiling(features); + startedAny = true; + } + } + if (startedAny) + d << idForObject(engine); + } else { + QSet<QQmlEngine *> engines; + for (QMultiHash<QQmlEngine *, QQmlAbstractProfilerAdapter *>::iterator i(m_engineProfilers.begin()); + i != m_engineProfilers.end(); ++i) { + if (!i.value()->isRunning()) { + engines << i.key(); + i.value()->startProfiling(features); + startedAny = true; + } + } + foreach (QQmlEngine *profiledEngine, engines) + d << idForObject(profiledEngine); + } + + if (startedAny) { + foreach (QQmlAbstractProfilerAdapter *profiler, m_globalProfilers) { + if (!profiler->isRunning()) + profiler->startProfiling(features); + } + + emit startFlushTimer(); + } + + emit messageToClient(name(), message); +} + +/*! + * Stop profiling the given \a engine. If \a engine is 0, stop all currently running engine + * profilers. + * + * If afterwards no more engine profilers are running, also stop all global profilers. Otherwise + * only make them report their data. + */ +void QQmlProfilerServiceImpl::stopProfiling(QQmlEngine *engine) +{ + QMutexLocker lock(&m_configMutex); + QList<QQmlAbstractProfilerAdapter *> stopping; + QList<QQmlAbstractProfilerAdapter *> reporting; + + bool stillRunning = false; + for (QMultiHash<QQmlEngine *, QQmlAbstractProfilerAdapter *>::iterator i(m_engineProfilers.begin()); + i != m_engineProfilers.end(); ++i) { + if (i.value()->isRunning()) { + if (engine == 0 || i.key() == engine) { + m_startTimes.insert(-1, i.value()); + stopping << i.value(); + } else { + stillRunning = true; + } + } + } + + if (stopping.isEmpty()) + return; + + foreach (QQmlAbstractProfilerAdapter *profiler, m_globalProfilers) { + if (!profiler->isRunning()) + continue; + m_startTimes.insert(-1, profiler); + if (stillRunning) { + reporting << profiler; + } else { + stopping << profiler; + } + } + + emit stopFlushTimer(); + m_waitingForStop = true; + + foreach (QQmlAbstractProfilerAdapter *profiler, reporting) + profiler->reportData(); + + foreach (QQmlAbstractProfilerAdapter *profiler, stopping) + profiler->stopProfiling(); +} + +/* + Send the queued up messages. +*/ +void QQmlProfilerServiceImpl::sendMessages() +{ + QList<QByteArray> messages; + + QByteArray data; + + if (m_waitingForStop) { + QQmlDebugStream traceEnd(&data, QIODevice::WriteOnly); + traceEnd << m_timer.nsecsElapsed() << (int)Event << (int)EndTrace; + + QSet<QQmlEngine *> seen; + foreach (QQmlAbstractProfilerAdapter *profiler, m_startTimes) { + for (QMultiHash<QQmlEngine *, QQmlAbstractProfilerAdapter *>::iterator i(m_engineProfilers.begin()); + i != m_engineProfilers.end(); ++i) { + if (i.value() == profiler && !seen.contains(i.key())) { + seen << i.key(); + traceEnd << idForObject(i.key()); + } + } + } + } + + while (!m_startTimes.empty()) { + QQmlAbstractProfilerAdapter *first = m_startTimes.begin().value(); + m_startTimes.erase(m_startTimes.begin()); + if (!m_startTimes.empty()) { + qint64 next = first->sendMessages(m_startTimes.begin().key(), messages); + if (next != -1) + m_startTimes.insert(next, first); + } else { + first->sendMessages(std::numeric_limits<qint64>::max(), messages); + } + } + + if (m_waitingForStop) { + //indicate completion + messages << data; + data.clear(); + + QQmlDebugStream ds(&data, QIODevice::WriteOnly); + ds << (qint64)-1 << (int)Complete; + messages << data; + m_waitingForStop = false; + } + + emit messagesToClient(name(), messages); + + // Restart flushing if any profilers are still running + foreach (const QQmlAbstractProfilerAdapter *profiler, m_engineProfilers) { + if (profiler->isRunning()) { + emit startFlushTimer(); + break; + } + } +} + +void QQmlProfilerServiceImpl::stateAboutToBeChanged(QQmlDebugService::State newState) +{ + QMutexLocker lock(&m_configMutex); + + if (state() == newState) + return; + + // Stop all profiling and send the data before we get disabled. + if (newState != Enabled) { + foreach (QQmlEngine *engine, m_engineProfilers.keys()) + stopProfiling(engine); + } +} + +void QQmlProfilerServiceImpl::messageReceived(const QByteArray &message) +{ + QMutexLocker lock(&m_configMutex); + + QByteArray rwData = message; + QQmlDebugStream stream(&rwData, QIODevice::ReadOnly); + + int engineId = -1; + quint64 features = std::numeric_limits<quint64>::max(); + bool enabled; + uint flushInterval = 0; + stream >> enabled; + if (!stream.atEnd()) + stream >> engineId; + if (!stream.atEnd()) + stream >> features; + if (!stream.atEnd()) { + stream >> flushInterval; + m_flushTimer.setInterval(flushInterval); + if (flushInterval > 0) { + connect(&m_flushTimer, SIGNAL(timeout()), this, SLOT(flush())); + connect(this, SIGNAL(startFlushTimer()), &m_flushTimer, SLOT(start())); + connect(this, SIGNAL(stopFlushTimer()), &m_flushTimer, SLOT(stop())); + } else { + disconnect(&m_flushTimer, SIGNAL(timeout()), this, SLOT(flush())); + disconnect(this, SIGNAL(startFlushTimer()), &m_flushTimer, SLOT(start())); + disconnect(this, SIGNAL(stopFlushTimer()), &m_flushTimer, SLOT(stop())); + } + } + + // If engineId == -1 objectForId() and then the cast will return 0. + if (enabled) + startProfiling(qobject_cast<QQmlEngine *>(objectForId(engineId)), features); + else + stopProfiling(qobject_cast<QQmlEngine *>(objectForId(engineId))); + + stopWaiting(); +} + +void QQmlProfilerServiceImpl::flush() +{ + QMutexLocker lock(&m_configMutex); + + foreach (QQmlAbstractProfilerAdapter *profiler, m_engineProfilers) { + if (profiler->isRunning()) { + m_startTimes.insert(-1, profiler); + profiler->reportData(); + } + } + + foreach (QQmlAbstractProfilerAdapter *profiler, m_globalProfilers) { + if (profiler->isRunning()) { + m_startTimes.insert(-1, profiler); + profiler->reportData(); + } + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.h b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.h new file mode 100644 index 0000000000..9b139ffabb --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $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 "qqmlconfigurabledebugservice.h" +#include <private/qqmldebugserviceinterfaces_p.h> +#include <private/qqmlprofilerdefinitions_p.h> +#include <private/qqmlabstractprofileradapter_p.h> +#include <private/qqmlboundsignal_p.h> + +#include <QtCore/qelapsedtimer.h> +#include <QtCore/qmetaobject.h> +#include <QtCore/qmutex.h> +#include <QtCore/qvector.h> +#include <QtCore/qstringbuilder.h> +#include <QtCore/qwaitcondition.h> +#include <QtCore/qtimer.h> + +#include <limits> + +QT_BEGIN_NAMESPACE + +class QUrl; +class QQmlEngine; + + +class QQmlProfilerServiceImpl : + public QQmlConfigurableDebugService<QQmlProfilerService>, + public QQmlProfilerDefinitions +{ + Q_OBJECT +public: + + void engineAboutToBeAdded(QQmlEngine *engine); + void engineAboutToBeRemoved(QQmlEngine *engine); + void engineAdded(QQmlEngine *engine); + void engineRemoved(QQmlEngine *engine); + + void addGlobalProfiler(QQmlAbstractProfilerAdapter *profiler); + void removeGlobalProfiler(QQmlAbstractProfilerAdapter *profiler); + + void startProfiling(QQmlEngine *engine, quint64 features = std::numeric_limits<quint64>::max()); + void stopProfiling(QQmlEngine *engine); + + QQmlProfilerServiceImpl(QObject *parent = 0); + ~QQmlProfilerServiceImpl(); + + void dataReady(QQmlAbstractProfilerAdapter *profiler); + +signals: + void startFlushTimer(); + void stopFlushTimer(); + +private slots: + void flush(); + +protected: + virtual void stateAboutToBeChanged(State state); + virtual void messageReceived(const QByteArray &); + +private: + friend class QQmlProfilerServiceFactory; + + void sendMessages(); + void addEngineProfiler(QQmlAbstractProfilerAdapter *profiler, QQmlEngine *engine); + void removeProfilerFromStartTimes(const QQmlAbstractProfilerAdapter *profiler); + + QElapsedTimer m_timer; + QTimer m_flushTimer; + bool m_waitingForStop; + + QList<QQmlAbstractProfilerAdapter *> m_globalProfilers; + QMultiHash<QQmlEngine *, QQmlAbstractProfilerAdapter *> m_engineProfilers; + QList<QQmlEngine *> m_stoppingEngines; + QMultiMap<qint64, QQmlAbstractProfilerAdapter *> m_startTimes; +}; + +QT_END_NAMESPACE + +#endif // QQMLPROFILERSERVICE_P_H + diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.json b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.json new file mode 100644 index 0000000000..ec1ec364da --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "CanvasFrameRate", "EngineControl" ] +} diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservicefactory.cpp b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservicefactory.cpp new file mode 100644 index 0000000000..83c2075246 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservicefactory.cpp @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlprofilerservice.h" +#include "qqmlenginecontrolservice.h" +#include "qqmlprofilerservicefactory.h" + +QT_BEGIN_NAMESPACE + +QQmlDebugService *QQmlProfilerServiceFactory::create(const QString &key) +{ + if (key == QQmlProfilerServiceImpl::s_key) + return new QQmlProfilerServiceImpl(this); + + if (key == QQmlEngineControlService::s_key) + return new QQmlEngineControlService(this); + + return 0; +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservicefactory.h b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservicefactory.h new file mode 100644 index 0000000000..b570136e5b --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservicefactory.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPROFILERSERVICE_H +#define QQMLPROFILERSERVICE_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/qqmldebugservicefactory_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlProfilerServiceFactory : public QQmlDebugServiceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlDebugServiceFactory_iid FILE "qqmlprofilerservice.json") +public: + QQmlDebugService *create(const QString &key); +}; + +QT_END_NAMESPACE + +#endif // QQMLPROFILERSERVICE_H diff --git a/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.cpp b/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.cpp new file mode 100644 index 0000000000..24e01f4c68 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.cpp @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qv4profileradapter.h" +#include "qqmlprofilerservice.h" + +QT_BEGIN_NAMESPACE + +QV4ProfilerAdapter::QV4ProfilerAdapter(QQmlProfilerService *service, QV4::ExecutionEngine *engine) : + QQmlAbstractProfilerAdapter(service), dataPos(0), memoryPos(0) +{ + engine->enableProfiler(); + connect(this, SIGNAL(profilingEnabled(quint64)), + engine->profiler, SLOT(startProfiling(quint64))); + connect(this, SIGNAL(profilingEnabledWhileWaiting(quint64)), + engine->profiler, SLOT(startProfiling(quint64)), Qt::DirectConnection); + connect(this, SIGNAL(profilingDisabled()), engine->profiler, SLOT(stopProfiling())); + connect(this, SIGNAL(profilingDisabledWhileWaiting()), engine->profiler, SLOT(stopProfiling()), + Qt::DirectConnection); + connect(this, SIGNAL(dataRequested()), engine->profiler, SLOT(reportData())); + connect(this, SIGNAL(referenceTimeKnown(QElapsedTimer)), + engine->profiler, SLOT(setTimer(QElapsedTimer))); + connect(engine->profiler, SIGNAL(dataReady(QVector<QV4::Profiling::FunctionCallProperties>, + QVector<QV4::Profiling::MemoryAllocationProperties>)), + this, SLOT(receiveData(QVector<QV4::Profiling::FunctionCallProperties>, + QVector<QV4::Profiling::MemoryAllocationProperties>))); +} + +qint64 QV4ProfilerAdapter::appendMemoryEvents(qint64 until, QList<QByteArray> &messages) +{ + QByteArray message; + while (memory_data.length() > memoryPos && memory_data[memoryPos].timestamp <= until) { + QQmlDebugStream d(&message, QIODevice::WriteOnly); + QV4::Profiling::MemoryAllocationProperties &props = memory_data[memoryPos]; + d << props.timestamp << MemoryAllocation << props.type << props.size; + ++memoryPos; + messages.append(message); + } + return memory_data.length() == memoryPos ? -1 : memory_data[memoryPos].timestamp; +} + +qint64 QV4ProfilerAdapter::finalizeMessages(qint64 until, QList<QByteArray> &messages, + qint64 callNext) +{ + if (callNext == -1) { + data.clear(); + dataPos = 0; + } + + qint64 memoryNext = appendMemoryEvents(until, messages); + + if (memoryNext == -1) { + memory_data.clear(); + memoryPos = 0; + return callNext; + } + + return callNext == -1 ? memoryNext : qMin(callNext, memoryNext); +} + +qint64 QV4ProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages) +{ + QByteArray message; + while (true) { + while (!stack.isEmpty() && (dataPos == data.length() || + stack.top() <= data[dataPos].start)) { + if (stack.top() > until) + return finalizeMessages(until, messages, stack.top()); + + appendMemoryEvents(stack.top(), messages); + QQmlDebugStream d(&message, QIODevice::WriteOnly); + d << stack.pop() << RangeEnd << Javascript; + messages.append(message); + } + while (dataPos != data.length() && (stack.empty() || data[dataPos].start < stack.top())) { + const QV4::Profiling::FunctionCallProperties &props = data[dataPos]; + if (props.start > until) + return finalizeMessages(until, messages, props.start); + + appendMemoryEvents(props.start, messages); + + QQmlDebugStream d_start(&message, QIODevice::WriteOnly); + d_start << props.start << RangeStart << Javascript; + messages.push_back(message); + message.clear(); + QQmlDebugStream d_location(&message, QIODevice::WriteOnly); + d_location << props.start << RangeLocation << Javascript << props.file << props.line + << props.column; + messages.push_back(message); + message.clear(); + QQmlDebugStream d_data(&message, QIODevice::WriteOnly); + d_data << props.start << RangeData << Javascript << props.name; + messages.push_back(message); + message.clear(); + stack.push(props.end); + ++dataPos; + } + if (stack.empty() && dataPos == data.length()) + return finalizeMessages(until, messages, -1); + } +} + +void QV4ProfilerAdapter::receiveData( + const QVector<QV4::Profiling::FunctionCallProperties> &new_data, + const QVector<QV4::Profiling::MemoryAllocationProperties> &new_memory_data) +{ + // In rare cases it could be that another flush or stop event is processed while data from + // the previous one is still pending. In that case we just append the data. + + if (data.isEmpty()) + data = new_data; + else + data.append(new_data); + + if (memory_data.isEmpty()) + memory_data = new_memory_data; + else + memory_data.append(new_memory_data); + + service->dataReady(this); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.h b/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.h new file mode 100644 index 0000000000..cea3da72e3 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QV4PROFILERADAPTER_P_H +#define QV4PROFILERADAPTER_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/qv4profiling_p.h> +#include <private/qqmlabstractprofileradapter_p.h> + +#include <QStack> +#include <QList> + +QT_BEGIN_NAMESPACE + +class QQmlProfilerService; +class QV4ProfilerAdapter : public QQmlAbstractProfilerAdapter { + Q_OBJECT + +public: + QV4ProfilerAdapter(QQmlProfilerService *service, QV4::ExecutionEngine *engine); + + virtual qint64 sendMessages(qint64 until, QList<QByteArray> &messages); + +public slots: + void receiveData(const QVector<QV4::Profiling::FunctionCallProperties> &, + const QVector<QV4::Profiling::MemoryAllocationProperties> &); + +private: + QVector<QV4::Profiling::FunctionCallProperties> data; + QVector<QV4::Profiling::MemoryAllocationProperties> memory_data; + int dataPos; + int memoryPos; + QStack<qint64> stack; + qint64 appendMemoryEvents(qint64 until, QList<QByteArray> &messages); + qint64 finalizeMessages(qint64 until, QList<QByteArray> &messages, qint64 callNext); +}; + +QT_END_NAMESPACE + +#endif // QV4PROFILERADAPTER_P_H diff --git a/src/plugins/qmltooling/qmldbg_qtquick2/qmldbg_qtquick2.pro b/src/plugins/qmltooling/qmldbg_qtquick2/qmldbg_qtquick2.pro deleted file mode 100644 index 2ee0d703b2..0000000000 --- a/src/plugins/qmltooling/qmldbg_qtquick2/qmldbg_qtquick2.pro +++ /dev/null @@ -1,27 +0,0 @@ -TARGET = qmldbg_qtquick2 -QT += qml-private quick-private core-private gui-private - -PLUGIN_TYPE = qmltooling -PLUGIN_CLASS_NAME = QtQuick2Plugin -load(qt_plugin) - -INCLUDEPATH *= $$PWD $$PWD/../shared - -SOURCES += \ - qtquick2plugin.cpp \ - highlight.cpp \ - qquickviewinspector.cpp \ - ../shared/abstracttool.cpp \ - ../shared/abstractviewinspector.cpp \ - inspecttool.cpp - -HEADERS += \ - qtquick2plugin.h \ - highlight.h \ - qquickviewinspector.h \ - ../shared/abstracttool.h \ - ../shared/abstractviewinspector.h \ - ../shared/qmlinspectorconstants.h \ - inspecttool.h - -OTHER_FILES += qtquick2plugin.json diff --git a/src/plugins/qmltooling/qmldbg_server/qmldbg_server.pro b/src/plugins/qmltooling/qmldbg_server/qmldbg_server.pro new file mode 100644 index 0000000000..5e2d0874df --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_server/qmldbg_server.pro @@ -0,0 +1,22 @@ +TARGET = qmldbg_server +QT = qml-private core-private + +PLUGIN_TYPE = qmltooling +PLUGIN_CLASS_NAME = QQmlDebugServerFactory +load(qt_plugin) + +SOURCES += \ + $$PWD/qqmldebugserver.cpp \ + $$PWD/../shared/qpacketprotocol.cpp + +HEADERS += \ + $$PWD/qqmldebugserverfactory.h \ + $$PWD/../shared/qqmldebugserver.h \ + $$PWD/../shared/qpacketprotocol.h \ + $$PWD/../shared/qqmldebugserverconnection.h + +INCLUDEPATH += $$PWD \ + $$PWD/../shared + +OTHER_FILES += \ + qqmldebugserver.json diff --git a/src/plugins/qmltooling/qmldbg_server/qqmldebugserver.cpp b/src/plugins/qmltooling/qmldbg_server/qqmldebugserver.cpp new file mode 100644 index 0000000000..69d36beaca --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_server/qqmldebugserver.cpp @@ -0,0 +1,693 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmldebugserver.h" +#include "qqmldebugserverfactory.h" +#include "qpacketprotocol.h" +#include "qqmldebugserverconnection.h" + +#include <private/qqmldebugservice_p.h> +#include <private/qqmlengine_p.h> +#include <private/qqmlglobal_p.h> +#include <private/qqmldebugpluginmanager_p.h> + +#include <QtCore/QAtomicInt> +#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 + "QDeclarativeDebugServer" 0 version pluginNames [QDataStream version] + version: an int representing the highest protocol version the client knows + pluginNames: plugins available on client side + 2. Server sends + "QDeclarativeDebugClient" 0 version pluginNames pluginVersions [QDataStream version] + 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 + "QDeclarativeDebugServer" 1 pluginNames + server plugin advertisement (not implemented: all services are required to register before open()) + 1. Server sends + "QDeclarativeDebugClient" 1 pluginNames pluginVersions + plugin communication: + Everything send with a header different to "QDeclarativeDebugServer" is sent to the appropriate plugin. + */ + +Q_QML_DEBUG_PLUGIN_LOADER(QQmlDebugServerConnection) +Q_QML_IMPORT_DEBUG_PLUGIN(QTcpServerConnectionFactory) +Q_QML_IMPORT_DEBUG_PLUGIN(QLocalClientConnectionFactory) + +const int protocolVersion = 1; + +class QQmlDebugServerImpl; +class QQmlDebugServerThread : public QThread +{ +public: + QQmlDebugServerThread() : m_server(0), m_portFrom(-1), m_portTo(-1) {} + + void setServer(QQmlDebugServerImpl *server) + { + m_server = server; + } + + void setPortRange(int portFrom, int portTo, const QString &hostAddress) + { + m_pluginName = QLatin1String("QTcpServerConnection"); + m_portFrom = portFrom; + m_portTo = portTo; + m_hostAddress = hostAddress; + } + + void setFileName(const QString &fileName) + { + m_pluginName = QLatin1String("QLocalClientConnection"); + m_fileName = fileName; + } + + void run(); + +private: + QQmlDebugServerImpl *m_server; + QString m_pluginName; + int m_portFrom; + int m_portTo; + QString m_hostAddress; + QString m_fileName; +}; + +class QQmlDebugServerImpl : public QQmlDebugServer +{ + Q_OBJECT +public: + QQmlDebugServerImpl(); + + bool blockingMode() const; + + QQmlDebugService *service(const QString &name) const; + + void addEngine(QQmlEngine *engine); + void removeEngine(QQmlEngine *engine); + + bool addService(const QString &name, QQmlDebugService *service); + bool removeService(const QString &name); + + bool open(const QVariantHash &configuration); + void setDevice(QIODevice *socket); + + void parseArguments(); + + static void cleanup(); + +private slots: + void wakeEngine(QQmlEngine *engine); + void sendMessage(const QString &name, const QByteArray &message); + void sendMessages(const QString &name, const QList<QByteArray> &messages); + void changeServiceState(const QString &serviceName, QQmlDebugService::State state); + void removeThread(); + void receiveMessage(); + void invalidPacket(); + +private: + friend class QQmlDebugServerThread; + friend class QQmlDebugServerFactory; + + class EngineCondition { + public: + EngineCondition() : numServices(0), condition(new QWaitCondition) {} + + bool waitForServices(QMutex *locked, int numEngines); + + void wake(); + private: + int numServices; + + // shared pointer to allow for QHash-inflicted copying. + QSharedPointer<QWaitCondition> condition; + }; + + bool canSendMessage(const QString &name); + void doSendMessage(const QString &name, const QByteArray &message); + + QQmlDebugServerConnection *m_connection; + QHash<QString, QQmlDebugService *> m_plugins; + QStringList m_clientPlugins; + bool m_gotHello; + bool m_blockingMode; + + QHash<QQmlEngine *, EngineCondition> m_engineConditions; + + QMutex m_helloMutex; + QWaitCondition m_helloCondition; + QQmlDebugServerThread m_thread; + QPacketProtocol *m_protocol; + QAtomicInt m_changeServiceStateCalls; +}; + +void QQmlDebugServerImpl::cleanup() +{ + QQmlDebugServerImpl *server = static_cast<QQmlDebugServerImpl *>( + QQmlDebugConnector::instance()); + if (!server) + return; + + for (QHash<QString, QQmlDebugService *>::ConstIterator i = server->m_plugins.constBegin(); + i != server->m_plugins.constEnd(); ++i) { + server->m_changeServiceStateCalls.ref(); + QMetaObject::invokeMethod(server, "changeServiceState", Qt::QueuedConnection, + Q_ARG(QString, i.key()), + Q_ARG(QQmlDebugService::State, + QQmlDebugService::NotConnected)); + } + + // Wait for changeServiceState calls to finish + // (while running an event loop because some services + // might again use slots to execute stuff in the GUI thread) + QEventLoop loop; + while (!server->m_changeServiceStateCalls.testAndSetOrdered(0, 0)) + loop.processEvents(); + + // Stop the thread while the application is still there. + server->m_thread.exit(); + server->m_thread.wait(); +} + +void QQmlDebugServerThread::run() +{ + Q_ASSERT_X(m_server != 0, Q_FUNC_INFO, "There should always be a debug server available here."); + QQmlDebugServerConnection *connection = loadQQmlDebugServerConnection(m_pluginName); + if (connection) { + { + QMutexLocker connectionLocker(&m_server->m_helloMutex); + m_server->m_connection = connection; + connection->setServer(m_server); + m_server->m_helloCondition.wakeAll(); + } + + if (m_fileName.isEmpty()) { + if (!connection->setPortRange(m_portFrom, m_portTo, m_server->blockingMode(), + m_hostAddress)) + return; + } else if (!connection->setFileName(m_fileName, m_server->blockingMode())) { + return; + } + + if (m_server->blockingMode()) + connection->waitForConnection(); + } else { + qWarning() << "QML Debugger: Couldn't load plugin" << m_pluginName; + return; + } + + exec(); + + // make sure events still waiting are processed + QEventLoop eventLoop; + eventLoop.processEvents(QEventLoop::AllEvents); +} + +bool QQmlDebugServerImpl::blockingMode() const +{ + return m_blockingMode; +} + +static void cleanupOnShutdown() +{ + // We cannot do this in the destructor as the connection plugin will get unloaded before the + // server plugin and we need the connection to send any remaining data. This function is + // triggered before any plugins are unloaded. + QQmlDebugServerImpl::cleanup(); +} + +QQmlDebugServerImpl::QQmlDebugServerImpl() : + m_connection(0), + m_gotHello(false), + m_blockingMode(false) +{ + static bool postRoutineAdded = false; + if (!postRoutineAdded) { + qAddPostRoutine(cleanupOnShutdown); + postRoutineAdded = true; + } + + // used in sendMessages + qRegisterMetaType<QList<QByteArray> >("QList<QByteArray>"); + // used in changeServiceState + qRegisterMetaType<QQmlDebugService::State>("QQmlDebugService::State"); + + m_thread.setServer(this); + moveToThread(&m_thread); + + // Remove the thread immmediately when it finishes, so that we don't have to wait for the + // event loop to signal that. + QObject::connect(&m_thread, SIGNAL(finished()), this, SLOT(removeThread()), + Qt::DirectConnection); + m_thread.setObjectName(QStringLiteral("QQmlDebugServerThread")); + parseArguments(); +} + +bool QQmlDebugServerImpl::open(const QVariantHash &configuration = QVariantHash()) +{ + if (m_thread.isRunning()) + return false; + if (!configuration.isEmpty()) { + m_blockingMode = configuration[QLatin1String("block")].toBool(); + if (configuration.contains(QLatin1String("portFrom"))) { + int portFrom = configuration[QLatin1String("portFrom")].toInt(); + int portTo = configuration[QLatin1String("portTo")].toInt(); + m_thread.setPortRange(portFrom, portTo == -1 ? portFrom : portTo, + configuration[QLatin1String("hostAddress")].toString()); + } else if (configuration.contains(QLatin1String("fileName"))) { + m_thread.setFileName(configuration[QLatin1String("fileName")].toString()); + } else { + return false; + } + } + + QMutexLocker locker(&m_helloMutex); + m_thread.start(); + m_helloCondition.wait(&m_helloMutex); // wait for connection + if (m_blockingMode && !m_gotHello) + m_helloCondition.wait(&m_helloMutex); // wait for hello + return true; +} + +void QQmlDebugServerImpl::parseArguments() +{ + // format: qmljsdebugger=port:<port_from>[,port_to],host:<ip address>][,block] + const QString args = commandLineArguments(); + if (args.isEmpty()) + return; // Manual initialization, through QQmlDebugServer::open() + + // ### remove port definition when protocol is changed + int portFrom = 0; + int portTo = 0; + bool block = false; + bool ok = false; + QString hostAddress; + QString fileName; + QStringList services; + + const QStringList lstjsDebugArguments = args.split(QLatin1Char(',')); + QStringList::const_iterator argsItEnd = lstjsDebugArguments.cend(); + QStringList::const_iterator argsIt = lstjsDebugArguments.cbegin(); + for (; argsIt != argsItEnd; ++argsIt) { + const QString strArgument = *argsIt; + if (strArgument.startsWith(QLatin1String("port:"))) { + portFrom = strArgument.mid(5).toInt(&ok); + portTo = portFrom; + QStringList::const_iterator argsNext = argsIt + 1; + if (argsNext == argsItEnd) + break; + const QString nextArgument = *argsNext; + + // Don't use QStringLiteral here. QRegExp has a global cache and will save an implicitly + // shared copy of the passed string. That copy isn't properly detached when the library + // is unloaded if the original string lives in the library's .rodata + if (ok && nextArgument.contains(QRegExp(QLatin1String("^\\s*\\d+\\s*$")))) { + portTo = nextArgument.toInt(&ok); + ++argsIt; + } + } else if (strArgument.startsWith(QLatin1String("host:"))) { + hostAddress = strArgument.mid(5); + } else if (strArgument == QLatin1String("block")) { + block = true; + } else if (strArgument.startsWith(QLatin1String("file:"))) { + fileName = strArgument.mid(5); + ok = !fileName.isEmpty(); + } else if (strArgument.startsWith(QLatin1String("services:"))) { + services.append(strArgument.mid(9)); + } else if (!services.isEmpty()) { + services.append(strArgument); + } else { + qWarning() << QString::fromLatin1("QML Debugger: Invalid argument '%1' " + "detected. Ignoring the same.") + .arg(strArgument); + } + } + + if (ok) { + setServices(services); + m_blockingMode = block; + if (!fileName.isEmpty()) + m_thread.setFileName(fileName); + else + m_thread.setPortRange(portFrom, portTo, hostAddress); + } else { + qWarning() << QString::fromLatin1("QML Debugger: Ignoring \"-qmljsdebugger=%1\". " + "Format is qmljsdebugger=port:<port_from>[,port_to],host:" + "<ip address>][,block]").arg(args); + } +} + +void QQmlDebugServerImpl::receiveMessage() +{ + typedef QHash<QString, QQmlDebugService*>::const_iterator DebugServiceConstIt; + + // to be executed in debugger thread + Q_ASSERT(QThread::currentThread() == thread()); + + if (!m_protocol) + return; + + QQmlDebugStream in(m_protocol->read().data()); + + QString name; + + in >> name; + if (name == QLatin1String("QDeclarativeDebugServer")) { + int op = -1; + in >> op; + if (op == 0) { + int version; + in >> version >> m_clientPlugins; + + //Get the supported QDataStream version + if (!in.atEnd()) { + in >> QQmlDebugStream::s_dataStreamVersion; + if (QQmlDebugStream::s_dataStreamVersion > QDataStream().version()) + QQmlDebugStream::s_dataStreamVersion = QDataStream().version(); + } + + // Send the hello answer immediately, since it needs to arrive before + // the plugins below start sending messages. + + QByteArray helloAnswer; + QQmlDebugStream out(&helloAnswer, QIODevice::WriteOnly); + QStringList pluginNames; + QList<float> pluginVersions; + const int count = m_plugins.count(); + pluginNames.reserve(count); + pluginVersions.reserve(count); + for (QHash<QString, QQmlDebugService *>::ConstIterator i = m_plugins.constBegin(); + i != m_plugins.constEnd(); ++i) { + pluginNames << i.key(); + pluginVersions << i.value()->version(); + } + + out << QString(QStringLiteral("QDeclarativeDebugClient")) << 0 << protocolVersion + << pluginNames << pluginVersions << QQmlDebugStream::s_dataStreamVersion; + + QPacket pack; + pack.writeRawData(helloAnswer.data(), helloAnswer.length()); + m_protocol->send(pack); + m_connection->flush(); + + QMutexLocker helloLock(&m_helloMutex); + m_gotHello = true; + + for (DebugServiceConstIt iter = m_plugins.constBegin(), cend = m_plugins.constEnd(); iter != cend; ++iter) { + QQmlDebugService::State newState = QQmlDebugService::Unavailable; + if (m_clientPlugins.contains(iter.key())) + newState = QQmlDebugService::Enabled; + m_changeServiceStateCalls.ref(); + changeServiceState(iter.key(), newState); + } + + m_helloCondition.wakeAll(); + + } else if (op == 1) { + // Service Discovery + QStringList oldClientPlugins = m_clientPlugins; + in >> m_clientPlugins; + + for (DebugServiceConstIt iter = m_plugins.constBegin(), cend = m_plugins.constEnd(); iter != cend; ++iter) { + const QString pluginName = iter.key(); + QQmlDebugService::State newState = QQmlDebugService::Unavailable; + if (m_clientPlugins.contains(pluginName)) + newState = QQmlDebugService::Enabled; + + if (oldClientPlugins.contains(pluginName) + != m_clientPlugins.contains(pluginName)) { + m_changeServiceStateCalls.ref(); + changeServiceState(iter.key(), newState); + } + } + + } else { + qWarning("QML Debugger: Invalid control message %d.", op); + invalidPacket(); + return; + } + + } else { + if (m_gotHello) { + QByteArray message; + in >> message; + + QHash<QString, QQmlDebugService *>::Iterator iter = m_plugins.find(name); + if (iter == m_plugins.end()) { + qWarning() << "QML Debugger: Message received for missing plugin" << name << '.'; + } else { + (*iter)->messageReceived(message); + } + } else { + qWarning("QML Debugger: Invalid hello message."); + } + + } +} + +void QQmlDebugServerImpl::changeServiceState(const QString &serviceName, + QQmlDebugService::State newState) +{ + // to be executed in debugger thread + Q_ASSERT(QThread::currentThread() == thread()); + + QQmlDebugService *service = m_plugins.value(serviceName); + if (service && service->state() != newState) { + service->stateAboutToBeChanged(newState); + service->setState(newState); + service->stateChanged(newState); + } + + m_changeServiceStateCalls.deref(); +} + +void QQmlDebugServerImpl::removeThread() +{ + Q_ASSERT(m_thread.isFinished()); + Q_ASSERT(QThread::currentThread() == thread()); + + QThread *parentThread = m_thread.thread(); + + delete m_connection; + m_connection = 0; + + // Move it back to the parent thread so that we can potentially restart it on a new thread. + moveToThread(parentThread); +} + +QQmlDebugService *QQmlDebugServerImpl::service(const QString &name) const +{ + return m_plugins.value(name); +} + +void QQmlDebugServerImpl::addEngine(QQmlEngine *engine) +{ + // to be executed outside of debugger thread + Q_ASSERT(QThread::currentThread() != &m_thread); + + QMutexLocker locker(&m_helloMutex); + foreach (QQmlDebugService *service, m_plugins) + service->engineAboutToBeAdded(engine); + + m_engineConditions[engine].waitForServices(&m_helloMutex, m_plugins.count()); + + foreach (QQmlDebugService *service, m_plugins) + service->engineAdded(engine); +} + +void QQmlDebugServerImpl::removeEngine(QQmlEngine *engine) +{ + // to be executed outside of debugger thread + Q_ASSERT(QThread::currentThread() != &m_thread); + + QMutexLocker locker(&m_helloMutex); + foreach (QQmlDebugService *service, m_plugins) + service->engineAboutToBeRemoved(engine); + + m_engineConditions[engine].waitForServices(&m_helloMutex, m_plugins.count()); + + foreach (QQmlDebugService *service, m_plugins) + service->engineRemoved(engine); +} + +bool QQmlDebugServerImpl::addService(const QString &name, QQmlDebugService *service) +{ + // to be executed before thread starts + Q_ASSERT(!m_thread.isRunning()); + + if (!service || m_plugins.contains(name)) + return false; + + connect(service, SIGNAL(messageToClient(QString,QByteArray)), + this, SLOT(sendMessage(QString,QByteArray))); + connect(service, SIGNAL(messagesToClient(QString,QList<QByteArray>)), + this, SLOT(sendMessages(QString,QList<QByteArray>))); + + connect(service, SIGNAL(attachedToEngine(QQmlEngine*)), + this, SLOT(wakeEngine(QQmlEngine*)), Qt::QueuedConnection); + connect(service, SIGNAL(detachedFromEngine(QQmlEngine*)), + this, SLOT(wakeEngine(QQmlEngine*)), Qt::QueuedConnection); + + service->setState(QQmlDebugService::Unavailable); + m_plugins.insert(name, service); + + return true; +} + +bool QQmlDebugServerImpl::removeService(const QString &name) +{ + // to be executed after thread ends + Q_ASSERT(!m_thread.isRunning()); + + QQmlDebugService *service = m_plugins.value(name); + if (!service) + return false; + + m_plugins.remove(name); + service->setState(QQmlDebugService::NotConnected); + + disconnect(service, SIGNAL(detachedFromEngine(QQmlEngine*)), + this, SLOT(wakeEngine(QQmlEngine*))); + disconnect(service, SIGNAL(attachedToEngine(QQmlEngine*)), + this, SLOT(wakeEngine(QQmlEngine*))); + + disconnect(service, SIGNAL(messagesToClient(QString,QList<QByteArray>)), + this, SLOT(sendMessages(QString,QList<QByteArray>))); + disconnect(service, SIGNAL(messageToClient(QString,QByteArray)), + this, SLOT(sendMessage(QString,QByteArray))); + + m_plugins.remove(service->name()); + + return true; +} + +bool QQmlDebugServerImpl::canSendMessage(const QString &name) +{ + // to be executed in debugger thread + Q_ASSERT(QThread::currentThread() == thread()); + return m_connection && m_connection->isConnected() && m_protocol && + m_clientPlugins.contains(name); +} + +void QQmlDebugServerImpl::doSendMessage(const QString &name, const QByteArray &message) +{ + QByteArray prefixed; + QQmlDebugStream out(&prefixed, QIODevice::WriteOnly); + out << name << message; + + QPacket pack; + pack.writeRawData(prefixed.data(), prefixed.length()); + m_protocol->send(pack); +} + +void QQmlDebugServerImpl::sendMessage(const QString &name, const QByteArray &message) +{ + if (canSendMessage(name)) { + doSendMessage(name, message); + m_connection->flush(); + } +} + +void QQmlDebugServerImpl::sendMessages(const QString &name, const QList<QByteArray> &messages) +{ + if (canSendMessage(name)) { + foreach (const QByteArray &message, messages) + doSendMessage(name, message); + m_connection->flush(); + } +} + +void QQmlDebugServerImpl::wakeEngine(QQmlEngine *engine) +{ + // to be executed in debugger thread + Q_ASSERT(QThread::currentThread() == thread()); + + QMutexLocker locker(&m_helloMutex); + m_engineConditions[engine].wake(); +} + +bool QQmlDebugServerImpl::EngineCondition::waitForServices(QMutex *locked, int num) +{ + Q_ASSERT_X(numServices == 0, Q_FUNC_INFO, "Request to wait again before previous wait finished"); + numServices = num; + return numServices > 0 ? condition->wait(locked) : true; +} + +void QQmlDebugServerImpl::EngineCondition::wake() +{ + if (--numServices == 0) + condition->wakeAll(); + Q_ASSERT_X(numServices >=0, Q_FUNC_INFO, "Woken more often than #services."); +} + +void QQmlDebugServerImpl::setDevice(QIODevice *socket) +{ + m_protocol = new QPacketProtocol(socket, this); + QObject::connect(m_protocol, SIGNAL(readyRead()), this, SLOT(receiveMessage())); + QObject::connect(m_protocol, SIGNAL(invalidPacket()), this, SLOT(invalidPacket())); + + if (blockingMode()) + m_protocol->waitForReadyRead(-1); +} + +void QQmlDebugServerImpl::invalidPacket() +{ + qWarning("QML Debugger: Received a corrupted packet! Giving up ..."); + m_connection->disconnect(); + // protocol might still be processing packages at this point + m_protocol->deleteLater(); + m_protocol = 0; +} + +QQmlDebugConnector *QQmlDebugServerFactory::create(const QString &key) +{ + // Cannot parent it to this because it gets moved to another thread + return (key == QLatin1String("QQmlDebugServer") ? new QQmlDebugServerImpl : 0); +} + +QT_END_NAMESPACE + +#include "qqmldebugserver.moc" diff --git a/src/plugins/qmltooling/qmldbg_server/qqmldebugserver.json b/src/plugins/qmltooling/qmldbg_server/qqmldebugserver.json new file mode 100644 index 0000000000..9b8dd3ae79 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_server/qqmldebugserver.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "QQmlDebugServer" ] +} diff --git a/src/plugins/qmltooling/qmldbg_server/qqmldebugserverfactory.h b/src/plugins/qmltooling/qmldbg_server/qqmldebugserverfactory.h new file mode 100644 index 0000000000..825a71bab8 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_server/qqmldebugserverfactory.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLDEBUGSERVERFACTORY_H +#define QQMLDEBUGSERVERFACTORY_H + +#include <private/qqmldebugconnector_p.h> + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// ementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +class QQmlDebugServerFactory : public QQmlDebugConnectorFactory +{ + Q_OBJECT + + // The interface for the code that loads this thing is QQmlDebugConnector. + // QQmlDebugServer is for connection plugins. + Q_PLUGIN_METADATA(IID QQmlDebugConnectorFactory_iid FILE "qqmldebugserver.json") +public: + QQmlDebugConnector *create(const QString &key); +}; + +QT_END_NAMESPACE + +#endif // QQMLDEBUGSERVERFACTORY_H diff --git a/src/plugins/qmltooling/qmldbg_tcp/qmldbg_tcp.pri b/src/plugins/qmltooling/qmldbg_tcp/qmldbg_tcp.pri deleted file mode 100644 index 4b78707c3d..0000000000 --- a/src/plugins/qmltooling/qmldbg_tcp/qmldbg_tcp.pri +++ /dev/null @@ -1,14 +0,0 @@ -QT += network core-private - -SOURCES += \ - $$PWD/qtcpserverconnection.cpp \ - $$PWD/../shared/qpacketprotocol.cpp - -HEADERS += \ - $$PWD/qtcpserverconnection.h \ - $$PWD/../shared/qpacketprotocol.h - -INCLUDEPATH += $$PWD \ - $$PWD/../shared - -OTHER_FILES += $$PWD/qtcpserverconnection.json diff --git a/src/plugins/qmltooling/qmldbg_tcp/qmldbg_tcp.pro b/src/plugins/qmltooling/qmldbg_tcp/qmldbg_tcp.pro index e1284b7233..fd419aeb56 100644 --- a/src/plugins/qmltooling/qmldbg_tcp/qmldbg_tcp.pro +++ b/src/plugins/qmltooling/qmldbg_tcp/qmldbg_tcp.pro @@ -1,8 +1,20 @@ TARGET = qmldbg_tcp -QT = qml-private core-private +QT = qml-private network PLUGIN_TYPE = qmltooling -PLUGIN_CLASS_NAME = QTcpServerConnection +PLUGIN_CLASS_NAME = QTcpServerConnectionFactory load(qt_plugin) -include(qmldbg_tcp.pri) +SOURCES += \ + $$PWD/qtcpserverconnection.cpp + +HEADERS += \ + $$PWD/qtcpserverconnectionfactory.h \ + $$PWD/../shared/qqmldebugserver.h \ + $$PWD/../shared/qqmldebugserverconnection.h + +INCLUDEPATH += $$PWD \ + $$PWD/../shared + +OTHER_FILES += \ + $$PWD/qtcpserverconnection.json diff --git a/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.cpp b/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.cpp index 2ae4edfce4..c8010a4aa9 100644 --- a/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.cpp +++ b/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.cpp @@ -31,136 +31,125 @@ ** ****************************************************************************/ -#include "qtcpserverconnection.h" -#include "qpacketprotocol.h" +#include "qtcpserverconnectionfactory.h" +#include "qqmldebugserver.h" #include <QtCore/qplugin.h> #include <QtNetwork/qtcpserver.h> #include <QtNetwork/qtcpsocket.h> -#include <private/qqmldebugserver_p.h> - QT_BEGIN_NAMESPACE -class QTcpServerConnectionPrivate { +class QTcpServerConnection : public QQmlDebugServerConnection +{ + Q_OBJECT + Q_DISABLE_COPY(QTcpServerConnection) + public: - QTcpServerConnectionPrivate(); + QTcpServerConnection(); + ~QTcpServerConnection(); - int portFrom; - int portTo; - bool block; - QString hostaddress; - QTcpSocket *socket; - QPacketProtocol *protocol; - QTcpServer *tcpServer; + void setServer(QQmlDebugServer *server); + bool setPortRange(int portFrom, int portTo, bool block, const QString &hostaddress); + bool setFileName(const QString &fileName, bool block); - QQmlDebugServer *debugServer; -}; + bool isConnected() const; + void disconnect(); -QTcpServerConnectionPrivate::QTcpServerConnectionPrivate() : - portFrom(0), - portTo(0), - block(false), - socket(0), - protocol(0), - tcpServer(0), - debugServer(0) -{ -} + void waitForConnection(); + void flush(); + +private slots: + void newConnection(); + +private: + bool listen(); + + int m_portFrom; + int m_portTo; + bool m_block; + QString m_hostaddress; + QTcpSocket *m_socket; + QTcpServer *m_tcpServer; + QQmlDebugServer *m_debugServer; +}; QTcpServerConnection::QTcpServerConnection() : - d_ptr(new QTcpServerConnectionPrivate) + m_portFrom(0), + m_portTo(0), + m_block(false), + m_socket(0), + m_tcpServer(0), + m_debugServer(0) { - } QTcpServerConnection::~QTcpServerConnection() { if (isConnected()) disconnect(); - delete d_ptr; } void QTcpServerConnection::setServer(QQmlDebugServer *server) { - Q_D(QTcpServerConnection); - d->debugServer = server; + m_debugServer = server; } bool QTcpServerConnection::isConnected() const { - Q_D(const QTcpServerConnection); - return d->socket && d->socket->state() == QTcpSocket::ConnectedState; -} - -void QTcpServerConnection::send(const QList<QByteArray> &messages) -{ - Q_D(QTcpServerConnection); - - if (!isConnected() - || !d->protocol || !d->socket) - return; - - foreach (const QByteArray &message, messages) { - QPacket pack; - pack.writeRawData(message.data(), message.length()); - d->protocol->send(pack); - } - d->socket->flush(); + return m_socket && m_socket->state() == QTcpSocket::ConnectedState; } void QTcpServerConnection::disconnect() { - Q_D(QTcpServerConnection); - - while (d->socket && d->socket->bytesToWrite() > 0) { - if (!d->socket->waitForBytesWritten()) { + while (m_socket && m_socket->bytesToWrite() > 0) { + if (!m_socket->waitForBytesWritten()) { qWarning("QML Debugger: Failed to send remaining %lld bytes on disconnect.", - d->socket->bytesToWrite()); + m_socket->bytesToWrite()); break; } } - // protocol might still be processing packages at this point - d->protocol->deleteLater(); - d->protocol = 0; - d->socket->deleteLater(); - d->socket = 0; -} - -bool QTcpServerConnection::waitForMessage() -{ - Q_D(QTcpServerConnection); - return d->protocol->waitForReadyRead(-1); + m_socket->deleteLater(); + m_socket = 0; } bool QTcpServerConnection::setPortRange(int portFrom, int portTo, bool block, const QString &hostaddress) { - Q_D(QTcpServerConnection); - d->portFrom = portFrom; - d->portTo = portTo; - d->block = block; - d->hostaddress = hostaddress; + m_portFrom = portFrom; + m_portTo = portTo; + m_block = block; + m_hostaddress = hostaddress; return listen(); } +bool QTcpServerConnection::setFileName(const QString &fileName, bool block) +{ + Q_UNUSED(fileName); + Q_UNUSED(block); + return false; +} + void QTcpServerConnection::waitForConnection() { - Q_D(QTcpServerConnection); - d->tcpServer->waitForNewConnection(-1); + m_tcpServer->waitForNewConnection(-1); } -bool QTcpServerConnection::listen() +void QTcpServerConnection::flush() { - Q_D(QTcpServerConnection); + if (m_socket) + m_socket->flush(); +} - d->tcpServer = new QTcpServer(this); - QObject::connect(d->tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection())); +bool QTcpServerConnection::listen() +{ + m_tcpServer = new QTcpServer(this); + QObject::connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection())); QHostAddress hostaddress; - if (!d->hostaddress.isEmpty()) { - if (!hostaddress.setAddress(d->hostaddress)) { + if (!m_hostaddress.isEmpty()) { + if (!hostaddress.setAddress(m_hostaddress)) { hostaddress = QHostAddress::Any; qDebug("QML Debugger: Incorrect host address provided. So accepting connections " "from any host."); @@ -168,64 +157,45 @@ bool QTcpServerConnection::listen() } else { hostaddress = QHostAddress::Any; } - int port = d->portFrom; + int port = m_portFrom; do { - if (d->tcpServer->listen(hostaddress, port)) { + if (m_tcpServer->listen(hostaddress, port)) { qDebug("QML Debugger: Waiting for connection on port %d...", port); break; } ++port; - } while (port <= d->portTo); - if (port > d->portTo) { - if (d->portFrom == d->portTo) - qWarning("QML Debugger: Unable to listen to port %d.", d->portFrom); + } while (port <= m_portTo); + if (port > m_portTo) { + if (m_portFrom == m_portTo) + qWarning("QML Debugger: Unable to listen to port %d.", m_portFrom); else - qWarning("QML Debugger: Unable to listen to ports %d - %d.", d->portFrom, d->portTo); + qWarning("QML Debugger: Unable to listen to ports %d - %d.", m_portFrom, m_portTo); return false; } else { return true; } } - -void QTcpServerConnection::readyRead() -{ - Q_D(QTcpServerConnection); - if (!d->protocol) - return; - - QPacket packet = d->protocol->read(); - - QByteArray content = packet.data(); - d->debugServer->receiveMessage(content); -} - void QTcpServerConnection::newConnection() { - Q_D(QTcpServerConnection); - - if (d->socket && d->socket->peerPort()) { + if (m_socket && m_socket->peerPort()) { qWarning("QML Debugger: Another client is already connected."); - QTcpSocket *faultyConnection = d->tcpServer->nextPendingConnection(); + QTcpSocket *faultyConnection = m_tcpServer->nextPendingConnection(); delete faultyConnection; return; } - delete d->socket; - d->socket = d->tcpServer->nextPendingConnection(); - d->socket->setParent(this); - d->protocol = new QPacketProtocol(d->socket, this); - QObject::connect(d->protocol, SIGNAL(readyRead()), this, SLOT(readyRead())); - QObject::connect(d->protocol, SIGNAL(invalidPacket()), this, SLOT(invalidPacket())); - - if (d->block) { - d->protocol->waitForReadyRead(-1); - } + delete m_socket; + m_socket = m_tcpServer->nextPendingConnection(); + m_socket->setParent(this); + m_debugServer->setDevice(m_socket); } -void QTcpServerConnection::invalidPacket() +QQmlDebugServerConnection *QTcpServerConnectionFactory::create(const QString &key) { - qWarning("QML Debugger: Received a corrupted packet! Giving up ..."); + return (key == QLatin1String("QTcpServerConnection") ? new QTcpServerConnection : 0); } QT_END_NAMESPACE + +#include "qtcpserverconnection.moc" diff --git a/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.json b/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.json new file mode 100644 index 0000000000..201a1b3fcb --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "QTcpServerConnection" ] +} diff --git a/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnectionfactory.h b/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnectionfactory.h new file mode 100644 index 0000000000..97dde03087 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnectionfactory.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTCPSERVERCONNECTIONFACTORY_H +#define QTCPSERVERCONNECTIONFACTORY_H + +#include "qqmldebugserverconnection.h" + +QT_BEGIN_NAMESPACE + +class QTcpServerConnectionFactory : public QQmlDebugServerConnectionFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlDebugServerConnectionFactory_iid FILE "qtcpserverconnection.json") + Q_INTERFACES(QQmlDebugServerConnectionFactory) +public: + QQmlDebugServerConnection *create(const QString &key); +}; + +QT_END_NAMESPACE + +#endif // QTCPSERVERCONNECTIONFACTORY_H diff --git a/src/plugins/qmltooling/qmltooling.pro b/src/plugins/qmltooling/qmltooling.pro index 3bc48a6b33..263e76e016 100644 --- a/src/plugins/qmltooling/qmltooling.pro +++ b/src/plugins/qmltooling/qmltooling.pro @@ -1,4 +1,15 @@ TEMPLATE = subdirs -SUBDIRS = qmldbg_tcp -qtHaveModule(quick): SUBDIRS += qmldbg_qtquick2 +# Connectors +SUBDIRS += \ + qmldbg_native \ + qmldbg_server \ + qmldbg_local \ + qmldbg_tcp + +# Services +SUBDIRS += \ + qmldbg_debugger \ + qmldbg_profiler + +qtHaveModule(quick): SUBDIRS += qmldbg_inspector diff --git a/src/plugins/qmltooling/shared/qqmlconfigurabledebugservice.h b/src/plugins/qmltooling/shared/qqmlconfigurabledebugservice.h new file mode 100644 index 0000000000..9aa4531428 --- /dev/null +++ b/src/plugins/qmltooling/shared/qqmlconfigurabledebugservice.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QQMLCONFIGURABLEDEBUGSEVICE_H +#define QQMLCONFIGURABLEDEBUGSEVICE_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 <private/qqmldebugconnector_p.h> +#include <QtCore/qmutex.h> + +QT_BEGIN_NAMESPACE + +template <class Base> +class QQmlConfigurableDebugService : public Base +{ +protected: + QQmlConfigurableDebugService(float version, QObject *parent = 0) : + Base(version, parent), m_configMutex(QMutex::Recursive) + { + init(); + } + + void stopWaiting() + { + QMutexLocker lock(&m_configMutex); + m_waitingForConfiguration = false; + foreach (QQmlEngine *engine, m_waitingEngines) + emit Base::attachedToEngine(engine); + m_waitingEngines.clear(); + } + + void init() + { + QMutexLocker lock(&m_configMutex); + // If we're not enabled or not blocking, don't wait for configuration + m_waitingForConfiguration = (Base::state() == QQmlDebugService::Enabled && + QQmlDebugConnector::instance()->blockingMode()); + } + + void stateChanged(QQmlDebugService::State newState) + { + if (newState != QQmlDebugService::Enabled) + stopWaiting(); + else + init(); + } + + void engineAboutToBeAdded(QQmlEngine *engine) + { + QMutexLocker lock(&m_configMutex); + if (m_waitingForConfiguration) + m_waitingEngines.append(engine); + else + emit Base::attachedToEngine(engine); + } + + QMutex m_configMutex; + QList<QQmlEngine *> m_waitingEngines; + bool m_waitingForConfiguration; +}; + +QT_END_NAMESPACE + +#endif // QQMLCONFIGURABLEDEBUGSEVICE_H diff --git a/src/plugins/qmltooling/shared/qmlinspectorconstants.h b/src/plugins/qmltooling/shared/qqmldebugserver.h index d76e172844..a7c17075d9 100644 --- a/src/plugins/qmltooling/shared/qmlinspectorconstants.h +++ b/src/plugins/qmltooling/shared/qqmldebugserver.h @@ -31,36 +31,32 @@ ** ****************************************************************************/ -#ifndef QMLINSPECTORCONSTANTS_H -#define QMLINSPECTORCONSTANTS_H - -#include <QtQml/private/qqmlglobal_p.h> - -namespace QmlJSDebugger { -namespace Constants { - -enum DesignTool { - NoTool = 0, - SelectionToolMode = 1, - MarqueeSelectionToolMode = 2, - MoveToolMode = 3, - ResizeToolMode = 4, - ZoomMode = 6 +#ifndef QQMLDEBUGSERVER_H +#define QQMLDEBUGSERVER_H + +#include <private/qqmldebugconnector_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_NAMESPACE + +class QQmlDebugServer : protected QQmlDebugConnector +{ + Q_OBJECT +public: + virtual void setDevice(QIODevice *socket) = 0; }; -static const int PressAndHoldTimeout = 800; - -static const double ZoomSnapDelta = 0.04; - -static const int EditorItemDataKey = 1000; - -enum GraphicsItemTypes { - EditorItemType = 0xEAAA, - ResizeHandleItemType = 0xEAEA -}; - - -} // namespace Constants -} // namespace QmlJSDebugger +QT_END_NAMESPACE -#endif // QMLINSPECTORCONSTANTS_H +#endif // QQMLDEBUGSERVER_H diff --git a/src/plugins/qmltooling/shared/qqmldebugserverconnection.h b/src/plugins/qmltooling/shared/qqmldebugserverconnection.h new file mode 100644 index 0000000000..9bdb1bcc8b --- /dev/null +++ b/src/plugins/qmltooling/shared/qqmldebugserverconnection.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLDEBUGSERVERCONNECTION_H +#define QQMLDEBUGSERVERCONNECTION_H + +#include <private/qtqmlglobal_p.h> +#include <QtCore/qobject.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_NAMESPACE + + +class QQmlDebugServer; +class QQmlDebugServerConnection : public QObject +{ + Q_OBJECT +public: + QQmlDebugServerConnection(QObject *parent = 0) : QObject(parent) {} + + virtual void setServer(QQmlDebugServer *server) = 0; + virtual bool setPortRange(int portFrom, int portTo, bool block, const QString &hostaddress) = 0; + virtual bool setFileName(const QString &fileName, bool block) = 0; + virtual bool isConnected() const = 0; + virtual void disconnect() = 0; + virtual void waitForConnection() = 0; + virtual void flush() = 0; +}; + +class QQmlDebugServerConnectionFactory : public QObject +{ + Q_OBJECT +public: + virtual QQmlDebugServerConnection *create(const QString &key) = 0; +}; + +#define QQmlDebugServerConnectionFactory_iid "org.qt-project.Qt.QQmlDebugServerConnectionFactory" +Q_DECLARE_INTERFACE(QQmlDebugServerConnectionFactory, QQmlDebugServerConnectionFactory_iid) + +QT_END_NAMESPACE + +#endif // QQMLDEBUGSERVERCONNECTION_H |