Commit b71c3c62 authored by Lasse Holmstedt's avatar Lasse Holmstedt

QML Debugger refactoring

Now, QmlEngine creates the connection to the inferior (debuggee), and
notifies QmlInspector when a connection is established. Before,
inspector created the debugger engin, which was wrong.

QmlEngine's responsibilities are connecting to the debuggee and basic
QML/JS debugging features like locals & watchers, breakpoints etc.
QmlInspector takes care of Live Preview and other fancy inspection
features.

Reviewed-by: hjk
parent 5de57dda
......@@ -112,7 +112,12 @@ include(cdb/cdb.pri)
include(gdb/gdb.pri)
include(script/script.pri)
include(pdb/pdb.pri)
include(qml/qml.pri)
contains(QT_CONFIG, declarative) {
QT += declarative
include(qml/qml.pri)
}
include(tcf/tcf.pri)
include(shared/shared.pri)
......
......@@ -76,6 +76,10 @@ public:
bool breakAtMain;
QString crashParameter; // for AttachCrashedExternal
// for qml debugging
QString qmlServerAddress;
quint16 qmlServerPort;
// for remote debugging
QString remoteChannel;
QString remoteArchitecture;
......
......@@ -331,7 +331,7 @@ void DebuggerRunControl::createEngine(const DebuggerStartParameters &sp)
QString errorMessage;
QString settingsIdHint;
if (sp.executable.endsWith(_("qmlviewer")))
if (sp.executable.endsWith(_("qmlviewer")) || sp.executable.endsWith(_("qmlobserver")))
engineType = QmlEngineType;
else if (sp.executable.endsWith(_(".js")))
engineType = ScriptEngineType;
......
include($$PWD/../../../libs/qmljsdebugclient/qmljsdebugclient-lib.pri)
HEADERS += $$PWD/qmlengine.h
SOURCES += $$PWD/qmlengine.cpp
HEADERS += \
$$PWD/qmlengine.h \
$$PWD/qmladapter.h \
$$PWD/qmldebuggerclient.h \
$$PWD/qmljsprivateapi.h
SOURCES += \
$$PWD/qmlengine.cpp \
$$PWD/qmladapter.cpp \
$$PWD/qmldebuggerclient.cpp
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** 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 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "qmladapter.h"
#include "qmldebuggerclient.h"
#include "qmljsprivateapi.h"
#include "debuggerengine.h"
#include <QAbstractSocket>
#include <QTimer>
#include <QDebug>
namespace Debugger {
namespace Internal {
QmlAdapter::QmlAdapter(DebuggerEngine *engine, QObject *parent)
: QObject(parent)
, m_engine(engine)
, m_qmlClient(0)
, m_mainClient(0)
, m_connectionTimer(new QTimer(this))
, m_connectionAttempts(0)
, m_conn(0)
{
connect(m_connectionTimer, SIGNAL(timeout()), SLOT(pollInferior()));
}
void QmlAdapter::beginConnection()
{
m_connectionTimer->start();
}
void QmlAdapter::pollInferior()
{
++m_connectionAttempts;
if (connectToViewer()) {
m_connectionTimer->stop();
m_connectionAttempts = 0;
} else if (m_connectionAttempts == m_maxConnectionAttempts) {
emit connectionStartupFailed();
m_connectionTimer->stop();
m_connectionAttempts = 0;
}
}
bool QmlAdapter::connectToViewer()
{
if (m_engine.isNull() || (m_conn && m_conn->state() != QAbstractSocket::UnconnectedState))
return false;
m_conn = new QDeclarativeDebugConnection(this);
connect(m_conn, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
SLOT(connectionStateChanged()));
connect(m_conn, SIGNAL(error(QAbstractSocket::SocketError)),
SLOT(connectionErrorOccurred()));
QString address = m_engine.data()->startParameters().qmlServerAddress;
QString port = QString::number(m_engine.data()->startParameters().qmlServerPort);
showConnectionStatusMessage(tr("Connect to debug server %1:%2").arg(address).arg(port));
m_conn->connectToHost(m_engine.data()->startParameters().qmlServerAddress,
m_engine.data()->startParameters().qmlServerPort);
// blocks until connected; if no connection is available, will fail immediately
if (!m_conn->waitForConnected())
return false;
return true;
}
void QmlAdapter::connectionErrorOccurred()
{
showConnectionErrorMessage(tr("Error: (%1) %2", "%1=error code, %2=error message")
.arg(m_conn->error()).arg(m_conn->errorString()));
// this is only an error if we are already connected and something goes wrong.
if (isConnected())
emit connectionError();
}
void QmlAdapter::connectionStateChanged()
{
switch (m_conn->state()) {
case QAbstractSocket::UnconnectedState:
{
showConnectionStatusMessage(tr("disconnected.\n\n"));
emit disconnected();
break;
}
case QAbstractSocket::HostLookupState:
showConnectionStatusMessage(tr("resolving host..."));
break;
case QAbstractSocket::ConnectingState:
showConnectionStatusMessage(tr("connecting to debug server..."));
break;
case QAbstractSocket::ConnectedState:
{
showConnectionStatusMessage(tr("connected.\n"));
if (!m_mainClient) {
m_mainClient = new QDeclarativeEngineDebug(m_conn, this);
}
createDebuggerClient();
//reloadEngines();
emit connected();
break;
}
case QAbstractSocket::ClosingState:
showConnectionStatusMessage(tr("closing..."));
break;
case QAbstractSocket::BoundState:
case QAbstractSocket::ListeningState:
break;
}
}
void QmlAdapter::createDebuggerClient()
{
m_qmlClient = new QmlDebuggerClient(m_conn);
connect(m_engine.data(), SIGNAL(sendMessage(QByteArray)),
m_qmlClient, SLOT(slotSendMessage(QByteArray)));
connect(m_qmlClient, SIGNAL(messageWasReceived(QByteArray)),
m_engine.data(), SLOT(messageReceived(QByteArray)));
//engine->startSuccessful(); // FIXME: AAA: port to new debugger states
}
bool QmlAdapter::isConnected() const
{
return m_conn && m_qmlClient && m_conn->state() == QAbstractSocket::ConnectedState;
}
bool QmlAdapter::isUnconnected() const
{
return !m_conn || m_conn->state() == QAbstractSocket::UnconnectedState;
}
QDeclarativeEngineDebug *QmlAdapter::client() const
{
return m_mainClient;
}
QDeclarativeDebugConnection *QmlAdapter::connection() const
{
if (!isConnected())
return 0;
return m_conn;
}
void QmlAdapter::showConnectionStatusMessage(const QString &message)
{
if (!m_engine.isNull())
m_engine.data()->showMessage(QLatin1String("QmlJSDebugger: ") + message, LogStatus);
}
void QmlAdapter::showConnectionErrorMessage(const QString &message)
{
if (!m_engine.isNull())
m_engine.data()->showMessage(QLatin1String("QmlJSDebugger: ") + message, LogError);
}
void QmlAdapter::setMaxConnectionAttempts(int maxAttempts)
{
m_maxConnectionAttempts = maxAttempts;
}
void QmlAdapter::setConnectionAttemptInterval(int interval)
{
m_connectionAttemptInterval = interval;
}
} // namespace Internal
} // namespace Debugger
......@@ -27,56 +27,72 @@
**
**************************************************************************/
#include "qmljsinspectorcontext.h"
#include "qmljsinspectorconstants.h"
#ifndef QMLADAPTER_H
#define QMLADAPTER_H
#include <coreplugin/icore.h>
#include <QtGui/QWidget>
#include <QtCore/QDebug>
#include <QObject>
#include <QWeakPointer>
using namespace QmlJSInspector::Internal;
using namespace QmlJSInspector::Constants;
#include "qmljsprivateapi.h"
#include "debugger_global.h"
InspectorContext::InspectorContext(QWidget *widget)
: IContext(widget),
m_widget(widget),
m_context(C_INSPECTOR)
{
}
QT_FORWARD_DECLARE_CLASS(QTimer)
InspectorContext::~InspectorContext()
{
}
namespace Debugger {
namespace Internal {
class DebuggerEngine;
class QmlDebuggerClient;
Core::Context InspectorContext::context() const
class DEBUGGER_EXPORT QmlAdapter : public QObject
{
return m_context;
}
Q_OBJECT
public:
explicit QmlAdapter(DebuggerEngine *engine, QObject *parent = 0);
void beginConnection();
QWidget *InspectorContext::widget()
{
return m_widget;
}
bool isConnected() const;
bool isUnconnected() const;
void InspectorContext::setContextHelpId(const QString &helpId)
{
m_contextHelpId = helpId;
}
QDeclarativeEngineDebug *client() const;
QDeclarativeDebugConnection *connection() const;
QString InspectorContext::contextHelpId() const
{
return m_contextHelpId;
}
// TODO move to private API b/w engine and adapter
void setMaxConnectionAttempts(int maxAttempts);
void setConnectionAttemptInterval(int interval);
QString InspectorContext::contextHelpIdForProperty(const QString &itemName, const QString &propName)
{
// TODO this functionality is not supported yet as we don't have help id's for
// properties.
return QString("QML.").append(itemName).append(".").append(propName);
}
signals:
void aboutToDisconnect();
void connected();
void disconnected();
void connectionStartupFailed();
void connectionError();
QString InspectorContext::contextHelpIdForItem(const QString &itemName)
{
return QString("QML.").append(itemName);
}
private slots:
void connectionErrorOccurred();
void connectionStateChanged();
void pollInferior();
private:
bool connectToViewer();
void createDebuggerClient();
void showConnectionStatusMessage(const QString &message);
void showConnectionErrorMessage(const QString &message);
private:
QWeakPointer<DebuggerEngine> m_engine;
QmlDebuggerClient *m_qmlClient;
QDeclarativeEngineDebug *m_mainClient;
QTimer *m_connectionTimer;
int m_connectionAttempts;
int m_maxConnectionAttempts;
int m_connectionAttemptInterval;
QDeclarativeDebugConnection *m_conn;
};
} // namespace Internal
} // namespace Debugger
#endif // QMLADAPTER_H
......@@ -26,29 +26,33 @@
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "qmljsdebuggerclient.h"
#include "qmldebuggerclient.h"
#include <extensionsystem/pluginmanager.h>
#include <utils/qtcassert.h>
using namespace QmlJSInspector::Internal;
namespace Debugger {
namespace Internal {
DebuggerClient::DebuggerClient(QDeclarativeDebugConnection* client)
QmlDebuggerClient::QmlDebuggerClient(QmlJsDebugClient::QDeclarativeDebugConnection* client)
: QDeclarativeDebugClient(QLatin1String("JSDebugger"), client)
{
setEnabled(true);
}
DebuggerClient::~DebuggerClient()
QmlDebuggerClient::~QmlDebuggerClient()
{
}
void DebuggerClient::messageReceived(const QByteArray &data)
void QmlDebuggerClient::messageReceived(const QByteArray &data)
{
emit messageWasReceived(data);
}
void DebuggerClient::slotSendMessage(const QByteArray &message)
void QmlDebuggerClient::slotSendMessage(const QByteArray &message)
{
QDeclarativeDebugClient::sendMessage(message);
}
} // Internal
} // Debugger
......@@ -32,16 +32,16 @@
#include "qmljsprivateapi.h"
namespace QmlJSInspector {
namespace Debugger {
namespace Internal {
class DebuggerClient : public QDeclarativeDebugClient
class QmlDebuggerClient : public QDeclarativeDebugClient
{
Q_OBJECT
public:
DebuggerClient(QDeclarativeDebugConnection *client);
virtual ~DebuggerClient();
QmlDebuggerClient(QmlJsDebugClient::QDeclarativeDebugConnection *client);
virtual ~QmlDebuggerClient();
signals:
void messageWasReceived(const QByteArray &data);
......
......@@ -28,6 +28,7 @@
**************************************************************************/
#include "qmlengine.h"
#include "qmladapter.h"
#include "debuggerconstants.h"
#include "debuggerplugin.h"
......@@ -42,6 +43,7 @@
#include "watchhandler.h"
#include "watchutils.h"
#include <extensionsystem/pluginmanager.h>
#include <projectexplorer/environment.h>
#include <utils/qtcassert.h>
......@@ -70,7 +72,10 @@
#endif
# define XSDEBUG(s) qDebug() << s
enum {
MaxConnectionAttempts = 50,
ConnectionAttemptDefaultInterval = 75
};
namespace Debugger {
namespace Internal {
......@@ -96,8 +101,12 @@ QDataStream& operator>>(QDataStream& s, WatchData &data)
///////////////////////////////////////////////////////////////////////
QmlEngine::QmlEngine(const DebuggerStartParameters &startParameters)
: DebuggerEngine(startParameters), m_ping(0)
: DebuggerEngine(startParameters)
, m_ping(0)
, m_adapter(new QmlAdapter(this))
, m_addedAdapterToObjectPool(false)
{
}
QmlEngine::~QmlEngine()
......@@ -107,32 +116,91 @@ QmlEngine::~QmlEngine()
void QmlEngine::setupInferior()
{
QTC_ASSERT(state() == InferiorSetupRequested, qDebug() << state());
attemptBreakpointSynchronization();
connect(&m_applicationLauncher, SIGNAL(processExited(int)), SLOT(disconnected()));
m_applicationLauncher.setEnvironment(startParameters().environment);
m_applicationLauncher.setWorkingDirectory(startParameters().workingDirectory);
notifyInferiorSetupOk();
}
void QmlEngine::connectionEstablished()
{
attemptBreakpointSynchronization();
ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
pluginManager->addObject(m_adapter);
m_addedAdapterToObjectPool = true;
plugin()->showMessage(tr("QML Debugger connected."), StatusBar);
notifyEngineRunAndInferiorRunOk();
}
void QmlEngine::connectionStartupFailed()
{
QMessageBox::critical(0,
tr("Failed to connect to debugger"),
tr("Could not connect to debugger server.") );
notifyEngineRunFailed();
}
void QmlEngine::connectionError()
{
// do nothing for now - only exit the debugger when inferior exits.
}
void QmlEngine::runEngine()
{
QTC_ASSERT(state() == EngineRunRequested, qDebug() << state());
notifyEngineRunAndInferiorRunOk();
// ### TODO for non-qmlproject apps, start in a different way
m_applicationLauncher.start(ProjectExplorer::ApplicationLauncher::Gui,
startParameters().executable,
startParameters().processArgs);
m_adapter->beginConnection();
plugin()->showMessage(tr("QML Debugger connecting..."), StatusBar);
}
void QmlEngine::shutdownInferior()
{
QTC_ASSERT(state() == InferiorShutdownRequested, qDebug() << state());
if (!m_applicationLauncher.isRunning()) {
showMessage(tr("Trying to stop while process is no longer running."), LogError);
} else {
disconnect(&m_applicationLauncher, SIGNAL(processExited(int)), this, SLOT(disconnected()));
m_applicationLauncher.stop();
}
notifyInferiorShutdownOk();
}
void QmlEngine::shutdownEngine()
{
QTC_ASSERT(state() == EngineShutdownRequested, qDebug() << state());
if (m_addedAdapterToObjectPool) {
ExtensionSystem::PluginManager *pluginManager = ExtensionSystem::PluginManager::instance();
pluginManager->removeObject(m_adapter);
}
if (m_applicationLauncher.isRunning()) {
// should only happen if engine is ill
disconnect(&m_applicationLauncher, SIGNAL(processExited(int)), this, SLOT(disconnected()));
m_applicationLauncher.stop();
}
notifyEngineShutdownOk();
}
const int serverPort = 3768;
void QmlEngine::setupEngine()
{
m_adapter->setMaxConnectionAttempts(MaxConnectionAttempts);
m_adapter->setConnectionAttemptInterval(ConnectionAttemptDefaultInterval);
connect(m_adapter, SIGNAL(connectionError()), SLOT(connectionError()));
connect(m_adapter, SIGNAL(connected()), SLOT(connectionEstablished()));
connect(m_adapter, SIGNAL(connectionStartupFailed()), SLOT(connectionStartupFailed()));
notifyEngineSetupOk();
}
......@@ -368,8 +436,6 @@ void QmlEngine::sendPing()
sendMessage(reply);
}
DebuggerEngine *createQmlEngine(const DebuggerStartParameters &sp)
{
return new QmlEngine(sp);
......@@ -520,6 +586,7 @@ void QmlEngine::messageReceived(const QByteArray &message)
void QmlEngine::disconnected()
{
plugin()->showMessage(tr("QML Debugger disconnected."), StatusBar);
notifyInferiorExited();
}
......
......@@ -44,12 +44,14 @@
#include <QtNetwork/QAbstractSocket>
#include <QtNetwork/QTcpSocket>
#include <projectexplorer/applicationlauncher.h>
namespace Debugger {
namespace Internal {
class ScriptAgent;
class WatchData;
class QmlAdapter;
class QmlResponse;
class QmlDebuggerClient;
......@@ -57,7 +59,6 @@ class DEBUGGER_EXPORT QmlEngine : public DebuggerEngine
{
Q_OBJECT
int m_ping;
public: