aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/plugins.pro2
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qdebugmessageservice.cpp96
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qdebugmessageservice.h (renamed from src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.h)61
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qmldbg_debugger.pro34
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservice.json3
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.cpp (renamed from src/plugins/qmltooling/qmldbg_qtquick2/qtquick2plugin.cpp)54
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qqmldebuggerservicefactory.h51
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.cpp820
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qqmlenginedebugservice.h135
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.cpp804
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qqmlnativedebugservice.h92
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.cpp181
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qqmlwatcher.h86
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp474
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h180
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.cpp253
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qv4debuggeragent.h97
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp931
-rw-r--r--src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h128
-rw-r--r--src/plugins/qmltooling/qmldbg_inspector/abstracttool.cpp (renamed from src/plugins/qmltooling/shared/abstracttool.cpp)4
-rw-r--r--src/plugins/qmltooling/qmldbg_inspector/abstracttool.h (renamed from src/plugins/qmltooling/shared/abstracttool.h)3
-rw-r--r--src/plugins/qmltooling/qmldbg_inspector/abstractviewinspector.cpp (renamed from src/plugins/qmltooling/shared/abstractviewinspector.cpp)21
-rw-r--r--src/plugins/qmltooling/qmldbg_inspector/abstractviewinspector.h (renamed from src/plugins/qmltooling/shared/abstractviewinspector.h)12
-rw-r--r--src/plugins/qmltooling/qmldbg_inspector/highlight.cpp (renamed from src/plugins/qmltooling/qmldbg_qtquick2/highlight.cpp)6
-rw-r--r--src/plugins/qmltooling/qmldbg_inspector/highlight.h (renamed from src/plugins/qmltooling/qmldbg_qtquick2/highlight.h)5
-rw-r--r--src/plugins/qmltooling/qmldbg_inspector/inspecttool.cpp (renamed from src/plugins/qmltooling/qmldbg_qtquick2/inspecttool.cpp)13
-rw-r--r--src/plugins/qmltooling/qmldbg_inspector/inspecttool.h (renamed from src/plugins/qmltooling/qmldbg_qtquick2/inspecttool.h)10
-rw-r--r--src/plugins/qmltooling/qmldbg_inspector/qmldbg_inspector.pro27
-rw-r--r--src/plugins/qmltooling/qmldbg_inspector/qqmlinspectorservice.cpp127
-rw-r--r--src/plugins/qmltooling/qmldbg_inspector/qqmlinspectorservice.json3
-rw-r--r--src/plugins/qmltooling/qmldbg_inspector/qqmlinspectorservicefactory.h66
-rw-r--r--src/plugins/qmltooling/qmldbg_inspector/qquickviewinspector.cpp (renamed from src/plugins/qmltooling/qmldbg_qtquick2/qquickviewinspector.cpp)14
-rw-r--r--src/plugins/qmltooling/qmldbg_inspector/qquickviewinspector.h (renamed from src/plugins/qmltooling/qmldbg_qtquick2/qquickviewinspector.h)7
-rw-r--r--src/plugins/qmltooling/qmldbg_local/qlocalclientconnection.cpp157
-rw-r--r--src/plugins/qmltooling/qmldbg_local/qlocalclientconnection.json3
-rw-r--r--src/plugins/qmltooling/qmldbg_local/qlocalclientconnectionfactory.h52
-rw-r--r--src/plugins/qmltooling/qmldbg_local/qmldbg_local.pro20
-rw-r--r--src/plugins/qmltooling/qmldbg_native/qmldbg_native.pro12
-rw-r--r--src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.cpp383
-rw-r--r--src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.json3
-rw-r--r--src/plugins/qmltooling/qmldbg_profiler/qmldbg_profiler.pro28
-rw-r--r--src/plugins/qmltooling/qmldbg_profiler/qqmlenginecontrolservice.cpp129
-rw-r--r--src/plugins/qmltooling/qmldbg_profiler/qqmlenginecontrolservice.h90
-rw-r--r--src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.cpp123
-rw-r--r--src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.h (renamed from src/plugins/qmltooling/qmldbg_qtquick2/qtquick2plugin.h)49
-rw-r--r--src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp426
-rw-r--r--src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.h124
-rw-r--r--src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.json3
-rw-r--r--src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservicefactory.cpp51
-rw-r--r--src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservicefactory.h62
-rw-r--r--src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.cpp153
-rw-r--r--src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.h81
-rw-r--r--src/plugins/qmltooling/qmldbg_qtquick2/qmldbg_qtquick2.pro27
-rw-r--r--src/plugins/qmltooling/qmldbg_server/qmldbg_server.pro22
-rw-r--r--src/plugins/qmltooling/qmldbg_server/qqmldebugserver.cpp693
-rw-r--r--src/plugins/qmltooling/qmldbg_server/qqmldebugserver.json3
-rw-r--r--src/plugins/qmltooling/qmldbg_server/qqmldebugserverfactory.h65
-rw-r--r--src/plugins/qmltooling/qmldbg_tcp/qmldbg_tcp.pri14
-rw-r--r--src/plugins/qmltooling/qmldbg_tcp/qmldbg_tcp.pro18
-rw-r--r--src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.cpp196
-rw-r--r--src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnection.json3
-rw-r--r--src/plugins/qmltooling/qmldbg_tcp/qtcpserverconnectionfactory.h52
-rw-r--r--src/plugins/qmltooling/qmltooling.pro15
-rw-r--r--src/plugins/qmltooling/shared/qqmlconfigurabledebugservice.h106
-rw-r--r--src/plugins/qmltooling/shared/qqmldebugserver.h (renamed from src/plugins/qmltooling/shared/qmlinspectorconstants.h)56
-rw-r--r--src/plugins/qmltooling/shared/qqmldebugserverconnection.h82
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