forked from qt-creator/qt-creator
qmlviewer: Add a javascript debugger.
The idea is to only enable it if it is run with -debugger, but for now it is always enabled for simplicity. This let you debug QML Javascript from creator
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the QtSCriptTools module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** No Commercial Usage
|
||||
** This file contains pre-release code and may not be distributed.
|
||||
** You may use this file in accordance with the terms and conditions
|
||||
** contained in the Technology Preview License Agreement accompanying
|
||||
** this package.
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
** In addition, as a special exception, Nokia gives you certain additional
|
||||
** rights. These rights are described in the Nokia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** If you have questions regarding the use of this file, please contact
|
||||
** Nokia at qt-info@nokia.com.
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "jsdebuggeragent.h"
|
||||
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#include <QtCore/qset.h>
|
||||
#include <QtScript/qscriptengine.h>
|
||||
#include <QtScript/QScriptContextInfo>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
/*!
|
||||
Constructs a new agent for the given \a engine. The agent will
|
||||
report debugging-related events (e.g. step completion) to the given
|
||||
\a backend.
|
||||
*/
|
||||
JSDebuggerAgent::JSDebuggerAgent(QScriptEngine *engine)
|
||||
: QDeclarativeDebugService("JSDebugger"), QScriptEngineAgent(engine)
|
||||
{}
|
||||
|
||||
/*!
|
||||
Destroys this QScriptDebuggerAgent.
|
||||
*/
|
||||
JSDebuggerAgent::~JSDebuggerAgent()
|
||||
{}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
*/
|
||||
void JSDebuggerAgent::scriptLoad(qint64 id, const QString & program,
|
||||
const QString &fileName, int )
|
||||
{
|
||||
filenames.insert(id, QUrl(fileName).toLocalFile());
|
||||
programs.insert(id, program);
|
||||
}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
*/
|
||||
void JSDebuggerAgent::scriptUnload(qint64 id)
|
||||
{
|
||||
filenames.remove(id);
|
||||
}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
*/
|
||||
void JSDebuggerAgent::contextPush()
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
*/
|
||||
void JSDebuggerAgent::contextPop()
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
*/
|
||||
void JSDebuggerAgent::functionEntry(qint64 scriptId)
|
||||
{
|
||||
Q_UNUSED(scriptId);
|
||||
stepDepth++;
|
||||
}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
*/
|
||||
void JSDebuggerAgent::functionExit(qint64 scriptId, const QScriptValue &returnValue)
|
||||
{
|
||||
Q_UNUSED(scriptId);
|
||||
Q_UNUSED(returnValue);
|
||||
stepDepth--;
|
||||
}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
*/
|
||||
void JSDebuggerAgent::positionChange(qint64 scriptId,
|
||||
int lineNumber, int columnNumber)
|
||||
{
|
||||
Q_UNUSED(columnNumber);
|
||||
|
||||
if(state == Stopped)
|
||||
return; //no re-entrency
|
||||
|
||||
// check breakpoints
|
||||
if (!breakpointList.isEmpty()) {
|
||||
QHash<qint64, QString>::const_iterator it = filenames.constFind(scriptId);
|
||||
if (it != filenames.constEnd()) {
|
||||
QPair<QString, qint32> key = qMakePair(*it, lineNumber);
|
||||
if (breakpointList.contains(key)) {
|
||||
stopped();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
switch (state) {
|
||||
case NoState:
|
||||
case Stopped:
|
||||
// Do nothing
|
||||
break;
|
||||
|
||||
case SteppingOutState:
|
||||
if (stepDepth >= 0)
|
||||
break;
|
||||
//fallthough
|
||||
case SteppingOverState:
|
||||
if (stepDepth > 0)
|
||||
break;
|
||||
//fallthough
|
||||
case SteppingIntoState:
|
||||
qDebug() << programs.value(scriptId);
|
||||
stopped();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
*/
|
||||
void JSDebuggerAgent::exceptionThrow(qint64 scriptId,
|
||||
const QScriptValue &exception,
|
||||
bool hasHandler)
|
||||
{
|
||||
Q_UNUSED(scriptId);
|
||||
Q_UNUSED(exception);
|
||||
Q_UNUSED(hasHandler);
|
||||
/* ... */
|
||||
}
|
||||
|
||||
/*!
|
||||
\reimp
|
||||
*/
|
||||
void JSDebuggerAgent::exceptionCatch(qint64 scriptId,
|
||||
const QScriptValue &exception)
|
||||
{
|
||||
Q_UNUSED(scriptId);
|
||||
Q_UNUSED(exception);
|
||||
}
|
||||
|
||||
void JSDebuggerAgent::messageReceived(const QByteArray& message)
|
||||
{
|
||||
QDataStream ds(message);
|
||||
QByteArray command;
|
||||
ds >> command;
|
||||
if (command == "BREAKPOINTS") {
|
||||
ds >> breakpointList;
|
||||
} else if (command == "WATCH_EXPRESSIONS") {
|
||||
ds >> watchExpressions;
|
||||
} else if (command == "STEPOVER") {
|
||||
stepDepth = 0;
|
||||
state = SteppingOverState;
|
||||
continueExec();
|
||||
} else if (command == "STEPINTO" || command == "INTERRUPT") {
|
||||
stepDepth = 0;
|
||||
state = SteppingIntoState;
|
||||
continueExec();
|
||||
} else if (command == "STEPOUT") {
|
||||
stepDepth = 0;
|
||||
state = SteppingOutState;
|
||||
continueExec();
|
||||
} else if (command == "CONTINUE") {
|
||||
state = NoState;
|
||||
continueExec();
|
||||
} else if (command == "EXEC") {
|
||||
State oldState = state;
|
||||
state = Stopped;
|
||||
QByteArray id;
|
||||
QString expr;
|
||||
ds >> id >> expr;
|
||||
|
||||
QVariant val = engine()->evaluate(expr).toVariant();
|
||||
// Clear any exceptions occurred during locals evaluation.
|
||||
engine()->clearExceptions();
|
||||
|
||||
QByteArray reply;
|
||||
QDataStream rs(&reply, QIODevice::WriteOnly);
|
||||
rs << QByteArray("RESULT") << id << expr << val;
|
||||
sendMessage(reply);
|
||||
state = oldState;
|
||||
|
||||
} else {
|
||||
qDebug() << Q_FUNC_INFO << "Unknown command" << command;
|
||||
}
|
||||
|
||||
QDeclarativeDebugService::messageReceived(message);
|
||||
}
|
||||
|
||||
void JSDebuggerAgent::stopped()
|
||||
{
|
||||
state = Stopped;
|
||||
QList<QPair<QString, QPair<QString, qint32> > > backtrace;
|
||||
|
||||
for (QScriptContext* ctx = engine()->currentContext(); ctx; ctx = ctx->parentContext()) {
|
||||
QScriptContextInfo info(ctx);
|
||||
|
||||
QString functionName = info.functionName();
|
||||
if (functionName.isEmpty()) {
|
||||
if (ctx->parentContext()) {
|
||||
switch (info.functionType()) {
|
||||
case QScriptContextInfo::ScriptFunction:
|
||||
functionName = QLatin1String("<anonymous>");
|
||||
break;
|
||||
case QScriptContextInfo::NativeFunction:
|
||||
functionName = QLatin1String("<native>");
|
||||
break;
|
||||
case QScriptContextInfo::QtFunction:
|
||||
case QScriptContextInfo::QtPropertyFunction:
|
||||
functionName = QLatin1String("<native slot>");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
functionName = QLatin1String("<global>");
|
||||
}
|
||||
}
|
||||
backtrace.append(qMakePair(functionName, qMakePair( QUrl(info.fileName()).toLocalFile(), info.lineNumber() ) ) );
|
||||
}
|
||||
QList<QPair<QString, QVariant> > watches;
|
||||
foreach (const QString &expr, watchExpressions) {
|
||||
watches << qMakePair(expr, engine()->evaluate(expr).toVariant());
|
||||
}
|
||||
|
||||
// Clear any exceptions occurred during locals evaluation.
|
||||
engine()->clearExceptions();
|
||||
|
||||
|
||||
QByteArray reply;
|
||||
QDataStream rs(&reply, QIODevice::WriteOnly);
|
||||
rs << QByteArray("STOPPED") << backtrace << watches;
|
||||
sendMessage(reply);
|
||||
|
||||
loop.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
|
||||
void JSDebuggerAgent::continueExec()
|
||||
{
|
||||
loop.quit();
|
||||
}
|
||||
|
||||
void JSDebuggerAgent::enabledChanged(bool on)
|
||||
{
|
||||
engine()->setAgent(on ? this : 0);
|
||||
QDeclarativeDebugService::enabledChanged(on);
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
@@ -0,0 +1,128 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the QtSCriptTools module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** No Commercial Usage
|
||||
** This file contains pre-release code and may not be distributed.
|
||||
** You may use this file in accordance with the terms and conditions
|
||||
** contained in the Technology Preview License Agreement accompanying
|
||||
** this package.
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
** In addition, as a special exception, Nokia gives you certain additional
|
||||
** rights. These rights are described in the Nokia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** If you have questions regarding the use of this file, please contact
|
||||
** Nokia at qt-info@nokia.com.
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QSCRIPTDEBUGGERAGENT_P_H
|
||||
#define QSCRIPTDEBUGGERAGENT_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 <QtScript/qscriptengineagent.h>
|
||||
#include <QtCore/QEventLoop>
|
||||
#include <QtCore/QSet>
|
||||
#include <private/qdeclarativedebugservice_p.h>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class JSDebuggerAgent : public QDeclarativeDebugService , public QScriptEngineAgent
|
||||
{ Q_OBJECT
|
||||
public:
|
||||
JSDebuggerAgent(QScriptEngine *engine);
|
||||
~JSDebuggerAgent();
|
||||
|
||||
// reimplemented
|
||||
void scriptLoad(qint64 id, const QString &program,
|
||||
const QString &fileName, int baseLineNumber);
|
||||
void scriptUnload(qint64 id);
|
||||
|
||||
void contextPush();
|
||||
void contextPop();
|
||||
|
||||
void functionEntry(qint64 scriptId);
|
||||
void functionExit(qint64 scriptId,
|
||||
const QScriptValue &returnValue);
|
||||
|
||||
void positionChange(qint64 scriptId,
|
||||
int lineNumber, int columnNumber);
|
||||
|
||||
void exceptionThrow(qint64 scriptId,
|
||||
const QScriptValue &exception,
|
||||
bool hasHandler);
|
||||
void exceptionCatch(qint64 scriptId,
|
||||
const QScriptValue &exception);
|
||||
|
||||
/* bool supportsExtension(Extension extension) const;
|
||||
QVariant extension(Extension extension,
|
||||
const QVariant &argument = QVariant());*/
|
||||
|
||||
void messageReceived(const QByteArray &);
|
||||
void enabledChanged(bool);
|
||||
|
||||
public slots:
|
||||
// void pauses();
|
||||
|
||||
private:
|
||||
enum State {
|
||||
NoState,
|
||||
SteppingIntoState,
|
||||
SteppingOverState,
|
||||
SteppingOutState,
|
||||
Stopped
|
||||
};
|
||||
State state;
|
||||
int stepDepth;
|
||||
int stepCount;
|
||||
|
||||
void continueExec();
|
||||
void stopped();
|
||||
|
||||
QEventLoop loop;
|
||||
QHash <qint64, QString> filenames;
|
||||
QHash <qint64, QString> programs;
|
||||
QSet< QPair<QString, qint32> > breakpointList;
|
||||
QStringList watchExpressions;
|
||||
|
||||
Q_DISABLE_COPY(JSDebuggerAgent)
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
@@ -226,6 +226,7 @@ int main(int argc, char ** argv)
|
||||
bool useNativeFileBrowser = true;
|
||||
bool experimentalGestures = false;
|
||||
bool designModeBehavior = false;
|
||||
bool debuggerModeBehavior = false;
|
||||
|
||||
WarningsConfig warningsConfig = DefaultWarnings;
|
||||
bool sizeToView = true;
|
||||
@@ -332,6 +333,8 @@ int main(int argc, char ** argv)
|
||||
experimentalGestures = true;
|
||||
} else if (arg == "-designmode") {
|
||||
designModeBehavior = true;
|
||||
} else if (arg == "-debugger") {
|
||||
debuggerModeBehavior = true;
|
||||
} else if (arg[0] != '-') {
|
||||
fileName = arg;
|
||||
} else if (1 || arg == "-help") {
|
||||
@@ -406,6 +409,7 @@ int main(int argc, char ** argv)
|
||||
viewer->enableExperimentalGestures();
|
||||
|
||||
viewer->setDesignModeBehavior(designModeBehavior);
|
||||
viewer->setDebugMode(debuggerModeBehavior);
|
||||
|
||||
foreach (QString lib, imports)
|
||||
viewer->addLibraryPath(lib);
|
||||
|
||||
@@ -14,14 +14,17 @@ HEADERS += $$PWD/qmlruntime.h \
|
||||
$$PWD/deviceorientation.h \
|
||||
$$PWD/loggerwidget.h \
|
||||
$$PWD/qdeclarativedesigndebugserver.h \
|
||||
$$PWD/qdeclarativedesignview.h
|
||||
$$PWD/qdeclarativedesignview.h \
|
||||
$$PWD/jsdebuggeragent.h
|
||||
|
||||
|
||||
SOURCES += $$PWD/qmlruntime.cpp \
|
||||
$$PWD/proxysettings.cpp \
|
||||
$$PWD/qdeclarativetester.cpp \
|
||||
$$PWD/loggerwidget.cpp \
|
||||
$$PWD/qdeclarativedesigndebugserver.cpp \
|
||||
$$PWD/qdeclarativedesignview.cpp
|
||||
$$PWD/qdeclarativedesignview.cpp \
|
||||
$$PWD/jsdebuggeragent.cpp
|
||||
|
||||
|
||||
RESOURCES += $$PWD/qmlruntime.qrc
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
#include "qdeclarative.h"
|
||||
#include <QAbstractAnimation>
|
||||
#include <private/qabstractanimation_p.h>
|
||||
#include <private/qdeclarativeengine_p.h>
|
||||
|
||||
#include <QSettings>
|
||||
#include <QXmlStreamReader>
|
||||
@@ -101,6 +102,7 @@
|
||||
#endif
|
||||
|
||||
#include <qdeclarativetester.h>
|
||||
#include "jsdebuggeragent.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@@ -634,6 +636,15 @@ void QDeclarativeViewer::setDesignModeBehavior(bool value)
|
||||
canvas->toolbar()->setEnabled(value);
|
||||
}
|
||||
|
||||
void QDeclarativeViewer::setDebugMode(bool on)
|
||||
{
|
||||
//if (on)
|
||||
{
|
||||
new JSDebuggerAgent(QDeclarativeEnginePrivate::getScriptEngine(canvas->engine()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void QDeclarativeViewer::enableExperimentalGestures()
|
||||
{
|
||||
canvas->viewport()->grabGesture(Qt::TapGesture,Qt::DontStartGestureOnChildren|Qt::ReceivePartialGestures|Qt::IgnoredGesturesPropagateToParent);
|
||||
|
||||
@@ -114,6 +114,7 @@ public:
|
||||
|
||||
public slots:
|
||||
void setDesignModeBehavior(bool value);
|
||||
void setDebugMode(bool on);
|
||||
void sceneResized(QSize size);
|
||||
bool open(const QString&);
|
||||
void openFile();
|
||||
|
||||
Reference in New Issue
Block a user