diff --git a/src/plugins/debugger/debugger.qbs b/src/plugins/debugger/debugger.qbs index cfb777f97c0..f682cdc4b57 100644 --- a/src/plugins/debugger/debugger.qbs +++ b/src/plugins/debugger/debugger.qbs @@ -146,12 +146,11 @@ QtcPlugin { prefix: "qml/" files: [ "interactiveinterpreter.cpp", "interactiveinterpreter.h", - "qmladapter.cpp", "qmladapter.h", "qmlcppengine.cpp", "qmlcppengine.h", "qmlengine.cpp", "qmlengine.h", + "qmlengineutils.cpp", "qmlengineutils.h", "qmlinspectoradapter.cpp", "qmlinspectoradapter.h", "qmlinspectoragent.cpp", "qmlinspectoragent.h", - "qmlv8debuggerclient.cpp", "qmlv8debuggerclient.h", "qmlv8debuggerclientconstants.h" ] } diff --git a/src/plugins/debugger/qml/qml.pri b/src/plugins/debugger/qml/qml.pri index 12d34e0dc8d..18ed7fe1f55 100644 --- a/src/plugins/debugger/qml/qml.pri +++ b/src/plugins/debugger/qml/qml.pri @@ -1,8 +1,7 @@ HEADERS += \ $$PWD/qmlengine.h \ - $$PWD/qmladapter.h \ + $$PWD/qmlengineutils.h \ $$PWD/qmlcppengine.h \ - $$PWD/qmlv8debuggerclient.h \ $$PWD/interactiveinterpreter.h \ $$PWD/qmlv8debuggerclientconstants.h \ $$PWD/qmlinspectoragent.h \ @@ -10,9 +9,8 @@ HEADERS += \ SOURCES += \ $$PWD/qmlengine.cpp \ - $$PWD/qmladapter.cpp \ + $$PWD/qmlengineutils.cpp \ $$PWD/qmlcppengine.cpp \ - $$PWD/qmlv8debuggerclient.cpp \ $$PWD/interactiveinterpreter.cpp \ $$PWD/qmlinspectoragent.cpp \ $$PWD/qmlinspectoradapter.cpp diff --git a/src/plugins/debugger/qml/qmladapter.cpp b/src/plugins/debugger/qml/qmladapter.cpp deleted file mode 100644 index c2698bdf17b..00000000000 --- a/src/plugins/debugger/qml/qmladapter.cpp +++ /dev/null @@ -1,233 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 The Qt Company Ltd. -** Contact: http://www.qt.io/licensing -** -** This file is part of Qt Creator. -** -** 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. -** -** In addition, 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. -** -****************************************************************************/ - -#include "qmladapter.h" - -#include -#include "qmlengine.h" -#include "qmlv8debuggerclient.h" - -#include - -#include - -using namespace QmlDebug; - -namespace Debugger { -namespace Internal { - -/*! - QmlAdapter manages the connection & clients for QML/JS debugging. - */ - -QmlAdapter::QmlAdapter(DebuggerEngine *engine, QObject *parent) - : QObject(parent) - , m_engine(engine) - , m_qmlClient(0) - , m_conn(0) - , m_msgClient(0) -{ - m_connectionTimer.setInterval(4000); - m_connectionTimer.setSingleShot(true); - connect(&m_connectionTimer, &QTimer::timeout, this, &QmlAdapter::checkConnectionState); - - m_conn = new QmlDebugConnection(this); - connect(m_conn, &QmlDebugConnection::stateMessage, - this, &QmlAdapter::showConnectionStateMessage); - connect(m_conn, &QmlDebugConnection::errorMessage, - this, &QmlAdapter::showConnectionErrorMessage); - connect(m_conn, &QmlDebugConnection::error, - this, &QmlAdapter::connectionErrorOccurred); - connect(m_conn, &QmlDebugConnection::opened, - &m_connectionTimer, &QTimer::stop); - connect(m_conn, &QmlDebugConnection::opened, - this, &QmlAdapter::connected); - connect(m_conn, &QmlDebugConnection::closed, - this, &QmlAdapter::disconnected); - - createDebuggerClients(); - m_msgClient = new QDebugMessageClient(m_conn); - connect(m_msgClient, &QDebugMessageClient::newState, this, &QmlAdapter::clientStateChanged); - -} - -QmlAdapter::~QmlAdapter() -{ -} - -void QmlAdapter::beginConnectionTcp(const QString &address, quint16 port) -{ - if (m_engine.isNull() || !m_conn || m_conn->isOpen()) - return; - - m_conn->connectToHost(address, port); - - //A timeout to check the connection state - m_connectionTimer.start(); -} - -void QmlAdapter::closeConnection() -{ - if (m_connectionTimer.isActive()) { - m_connectionTimer.stop(); - } else { - if (m_conn) - m_conn->close(); - } -} - -void QmlAdapter::connectionErrorOccurred(QDebugSupport::Error error) -{ - // this is only an error if we are already connected and something goes wrong. - if (isConnected()) { - emit connectionError(error); - } else { - m_connectionTimer.stop(); - emit connectionStartupFailed(); - } -} - -void QmlAdapter::clientStateChanged(QmlDebugClient::State state) -{ - QString serviceName; - float version = 0; - if (QmlDebugClient *client = qobject_cast(sender())) { - serviceName = client->name(); - version = client->remoteVersion(); - } - - logServiceStateChange(serviceName, version, state); -} - -void QmlAdapter::debugClientStateChanged(QmlDebugClient::State state) -{ - if (state != QmlDebugClient::Enabled) - return; - QmlDebugClient *client = qobject_cast(sender()); - QTC_ASSERT(client, return); - - m_qmlClient = qobject_cast(client); - m_qmlClient->startSession(); -} - -void QmlAdapter::checkConnectionState() -{ - if (!isConnected()) { - closeConnection(); - emit connectionStartupFailed(); - } -} - -bool QmlAdapter::isConnected() const -{ - return m_conn && m_qmlClient && m_conn->isOpen(); -} - -void QmlAdapter::createDebuggerClients() -{ - QmlV8DebuggerClient *debugClient2 = new QmlV8DebuggerClient(m_conn); - connect(debugClient2, &QmlV8DebuggerClient::newState, - this, &QmlAdapter::clientStateChanged); - connect(debugClient2, &QmlV8DebuggerClient::newState, - this, &QmlAdapter::debugClientStateChanged); - - m_debugClients.insert(debugClient2->name(),debugClient2); - - debugClient2->setEngine((QmlEngine*)(m_engine.data())); -} - -QmlDebugConnection *QmlAdapter::connection() const -{ - return m_conn; -} - -DebuggerEngine *QmlAdapter::debuggerEngine() const -{ - return m_engine.data(); -} - -void QmlAdapter::showConnectionStateMessage(const QString &message) -{ - if (!m_engine.isNull()) - m_engine.data()->showMessage(_("QML Debugger: ") + message, LogStatus); -} - -void QmlAdapter::showConnectionErrorMessage(const QString &message) -{ - if (!m_engine.isNull()) - m_engine.data()->showMessage(_("QML Debugger: ") + message, LogError); -} - -QmlV8DebuggerClient *QmlAdapter::activeDebuggerClient() const -{ - return m_qmlClient; -} - -QHash QmlAdapter::debuggerClients() const -{ - return m_debugClients; -} - -QDebugMessageClient *QmlAdapter::messageClient() const -{ - return m_msgClient; -} - -void QmlAdapter::logServiceStateChange(const QString &service, float version, - QmlDebugClient::State newState) -{ - switch (newState) { - case QmlDebugClient::Unavailable: { - showConnectionStateMessage(_("Status of \"%1\" Version: %2 changed to 'unavailable'."). - arg(service).arg(QString::number(version))); - break; - } - case QmlDebugClient::Enabled: { - showConnectionStateMessage(_("Status of \"%1\" Version: %2 changed to 'enabled'."). - arg(service).arg(QString::number(version))); - break; - } - - case QmlDebugClient::NotConnected: { - showConnectionStateMessage(_("Status of \"%1\" Version: %2 changed to 'not connected'."). - arg(service).arg(QString::number(version))); - break; - } - } -} - -void QmlAdapter::logServiceActivity(const QString &service, const QString &logMessage) -{ - if (!m_engine.isNull()) - m_engine.data()->showMessage(service + QLatin1Char(' ') + logMessage, LogDebug); -} - -} // namespace Internal -} // namespace Debugger diff --git a/src/plugins/debugger/qml/qmladapter.h b/src/plugins/debugger/qml/qmladapter.h deleted file mode 100644 index 7e966e4068f..00000000000 --- a/src/plugins/debugger/qml/qmladapter.h +++ /dev/null @@ -1,107 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 The Qt Company Ltd. -** Contact: http://www.qt.io/licensing -** -** This file is part of Qt Creator. -** -** 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. -** -** In addition, 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. -** -****************************************************************************/ - -#ifndef QMLADAPTER_H -#define QMLADAPTER_H - -#include - -#include -#include - -namespace QmlDebug { -class BaseEngineDebugClient; -class QmlDebugConnection; -class QDebugMessageClient; -} - -namespace Debugger { -namespace Internal { - -class QmlV8DebuggerClient; -class DebuggerEngine; -class QmlAdapterPrivate; - -class QmlAdapter : public QObject -{ - Q_OBJECT - -public: - explicit QmlAdapter(DebuggerEngine *engine, QObject *parent = 0); - virtual ~QmlAdapter(); - - void beginConnectionTcp(const QString &address, quint16 port); - void closeConnection(); - - QmlDebug::QmlDebugConnection *connection() const; - DebuggerEngine *debuggerEngine() const; - - QmlV8DebuggerClient *activeDebuggerClient() const; - QHash debuggerClients() const; - - QmlDebug::QDebugMessageClient *messageClient() const; - -public slots: - void logServiceStateChange(const QString &service, float version, - QmlDebug::QmlDebugClient::State newState); - void logServiceActivity(const QString &service, const QString &logMessage); - -signals: - void connected(); - void disconnected(); - void connectionStartupFailed(); - void connectionError(QDebugSupport::Error error); - void serviceConnectionError(const QString serviceName); - -private slots: - void connectionErrorOccurred(QDebugSupport::Error socketError); - void clientStateChanged(QmlDebug::QmlDebugClient::State state); - void debugClientStateChanged(QmlDebug::QmlDebugClient::State state); - void checkConnectionState(); - void showConnectionStateMessage(const QString &message); - void showConnectionErrorMessage(const QString &message); - -private: - bool isConnected() const; - void createDebuggerClients(); - -private: - QPointer m_engine; - QmlV8DebuggerClient *m_qmlClient; - QTimer m_connectionTimer; - QmlDebug::QmlDebugConnection *m_conn; - QHash m_debugClients; - QmlDebug::QDebugMessageClient *m_msgClient; -}; - -} // namespace Internal -} // namespace Debugger - -#endif // QMLADAPTER_H diff --git a/src/plugins/debugger/qml/qmlengine.cpp b/src/plugins/debugger/qml/qmlengine.cpp index ea1aabbb622..753ca5d65f9 100644 --- a/src/plugins/debugger/qml/qmlengine.cpp +++ b/src/plugins/debugger/qml/qmlengine.cpp @@ -29,46 +29,54 @@ ****************************************************************************/ #include "qmlengine.h" -#include "qmlinspectoragent.h" -#include "qmlv8debuggerclient.h" +#include "interactiveinterpreter.h" +#include "qmlinspectoradapter.h" +#include "qmlinspectoragent.h" +#include "qmlv8debuggerclientconstants.h" +#include "qmlengineutils.h" + +#include #include #include #include -#include #include -#include #include #include -#include +#include +#include #include +#include #include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include -#include #include #include #include +#include + +#include +#include +#include + +#include +#include + +#include + #include #include #include +#include +#include +#include +#include #include #include +#include +#include -#define DEBUG_QML 1 +#define DEBUG_QML 0 #if DEBUG_QML # define SDEBUG(s) qDebug() << s #else @@ -76,185 +84,141 @@ #endif # define XSDEBUG(s) qDebug() << s +using namespace Core; +using namespace ProjectExplorer; +using namespace QmlDebug; using namespace QmlJS; -using namespace AST; +using namespace TextEditor; namespace Debugger { namespace Internal { -static QTreeView *inspectorTreeView() +enum Exceptions { - return Internal::inspectorView(); -} - -class ASTWalker : public Visitor -{ -public: - void operator()(Node *ast, quint32 *l, quint32 *c) - { - done = false; - line = l; - column = c; - Node::accept(ast, this); - } - - bool preVisit(Node *ast) - { - return ast->lastSourceLocation().startLine >= *line && !done; - } - - //Case 1: Breakpoint is between sourceStart(exclusive) and - // sourceEnd(inclusive) --> End tree walk. - //Case 2: Breakpoint is on sourceStart --> Check for the start - // of the first executable code. Set the line number and - // column number. End tree walk. - //Case 3: Breakpoint is on "unbreakable" code --> Find the next "breakable" - // code and check for Case 2. End tree walk. - - //Add more types when suitable. - - bool visit(UiScriptBinding *ast) - { - if (!ast->statement) - return true; - - quint32 sourceStartLine = ast->firstSourceLocation().startLine; - quint32 statementStartLine; - quint32 statementColumn; - - if (ast->statement->kind == Node::Kind_ExpressionStatement) { - statementStartLine = ast->statement->firstSourceLocation(). - startLine; - statementColumn = ast->statement->firstSourceLocation().startColumn; - - } else if (ast->statement->kind == Node::Kind_Block) { - Block *block = static_cast(ast->statement); - if (!block || !block->statements) - return true; - statementStartLine = block->statements->firstSourceLocation(). - startLine; - statementColumn = block->statements->firstSourceLocation(). - startColumn; - - } else { - return true; - } - - - //Case 1 - //Check for possible relocation within the binding statement - - //Rewritten to (function () { { }}) - //The offset 16 is position of inner lbrace without token length. - const int offset = 16; - - //Case 2 - if (statementStartLine == *line) { - if (sourceStartLine == *line) - *column = offset + ast->qualifiedId->identifierToken.length; - done = true; - } - - //Case 3 - if (statementStartLine > *line) { - *line = statementStartLine; - if (sourceStartLine == *line) - *column = offset + ast->qualifiedId->identifierToken.length; - else - *column = statementColumn; - done = true; - } - return true; - } - - bool visit(FunctionDeclaration *ast) { - quint32 sourceStartLine = ast->firstSourceLocation().startLine; - quint32 sourceStartColumn = ast->firstSourceLocation().startColumn; - quint32 statementStartLine = ast->body->firstSourceLocation().startLine; - quint32 statementColumn = ast->body->firstSourceLocation().startColumn; - - //Case 1 - //Check for possible relocation within the function declaration - - //Case 2 - if (statementStartLine == *line) { - if (sourceStartLine == *line) - *column = statementColumn - sourceStartColumn + 1; - done = true; - } - - //Case 3 - if (statementStartLine > *line) { - *line = statementStartLine; - if (sourceStartLine == *line) - *column = statementColumn - sourceStartColumn + 1; - else - *column = statementColumn; - done = true; - } - return true; - } - - bool visit(EmptyStatement *ast) - { - *line = ast->lastSourceLocation().startLine + 1; - return true; - } - - bool visit(VariableStatement *ast) { test(ast); return true; } - bool visit(VariableDeclarationList *ast) { test(ast); return true; } - bool visit(VariableDeclaration *ast) { test(ast); return true; } - bool visit(ExpressionStatement *ast) { test(ast); return true; } - bool visit(IfStatement *ast) { test(ast); return true; } - bool visit(DoWhileStatement *ast) { test(ast); return true; } - bool visit(WhileStatement *ast) { test(ast); return true; } - bool visit(ForStatement *ast) { test(ast); return true; } - bool visit(LocalForStatement *ast) { test(ast); return true; } - bool visit(ForEachStatement *ast) { test(ast); return true; } - bool visit(LocalForEachStatement *ast) { test(ast); return true; } - bool visit(ContinueStatement *ast) { test(ast); return true; } - bool visit(BreakStatement *ast) { test(ast); return true; } - bool visit(ReturnStatement *ast) { test(ast); return true; } - bool visit(WithStatement *ast) { test(ast); return true; } - bool visit(SwitchStatement *ast) { test(ast); return true; } - bool visit(CaseBlock *ast) { test(ast); return true; } - bool visit(CaseClauses *ast) { test(ast); return true; } - bool visit(CaseClause *ast) { test(ast); return true; } - bool visit(DefaultClause *ast) { test(ast); return true; } - bool visit(LabelledStatement *ast) { test(ast); return true; } - bool visit(ThrowStatement *ast) { test(ast); return true; } - bool visit(TryStatement *ast) { test(ast); return true; } - bool visit(Catch *ast) { test(ast); return true; } - bool visit(Finally *ast) { test(ast); return true; } - bool visit(FunctionExpression *ast) { test(ast); return true; } - bool visit(DebuggerStatement *ast) { test(ast); return true; } - - void test(Node *ast) - { - quint32 statementStartLine = ast->firstSourceLocation().startLine; - //Case 1/2 - if (statementStartLine <= *line && - *line <= ast->lastSourceLocation().startLine) - done = true; - - //Case 3 - if (statementStartLine > *line) { - *line = statementStartLine; - *column = ast->firstSourceLocation().startColumn; - done = true; - } - } - - bool done; - quint32 *line; - quint32 *column; + NoExceptions, + UncaughtExceptions, + AllExceptions }; -ConsoleManagerInterface *qmlConsoleManager() +enum StepAction { - return ConsoleManagerInterface::instance(); + Continue, + StepIn, + StepOut, + Next +}; + +struct QmlV8ObjectData +{ + int handle; + QByteArray name; + QByteArray type; + QVariant value; + QVariantList properties; +}; + +class QmlEnginePrivate : QmlDebugClient +{ +public: + QmlEnginePrivate(QmlEngine *engine_, QmlDebugConnection *connection_) + : QmlDebugClient(QLatin1String("V8Debugger"), connection_), + engine(engine_), + inspectorAdapter(engine, connection_), + connection(connection_) + {} + + void sendMessage(const QByteArray &msg); + void messageReceived(const QByteArray &data); + void stateChanged(State state); + + void connect(); + void disconnect(); + + void continueDebugging(StepAction stepAction); + + void evaluate(const QString expr, bool global = false, bool disableBreak = false, + int frame = -1, bool addContext = false); + void lookup(const QList handles, bool includeSource = false); + void backtrace(int fromFrame = -1, int toFrame = -1, bool bottom = false); + void frame(int number = -1); + void scope(int number = -1, int frameNumber = -1); + void scripts(int types = 4, const QList ids = QList(), + bool includeSource = false, const QVariant filter = QVariant()); + + void setBreakpoint(const QString type, const QString target, + bool enabled = true,int line = 0, int column = 0, + const QString condition = QString(), int ignoreCount = -1); + void clearBreakpoint(int breakpoint); + void setExceptionBreak(Exceptions type, bool enabled = false); + + void version(); + void clearCache(); + + void sendAndLogV8Request(const QJsonObject &request); + + QByteArray packMessage(const QByteArray &type, const QByteArray &message = QByteArray()); + QJsonObject initObject(); + + void expandObject(const QByteArray &iname, quint64 objectId); + void flushSendBuffer(); + + void updateStack(const QVariant &bodyVal, const QVariant &refsVal); + void expandLocalsAndWatchers(const QVariant &bodyVal, const QVariant &refsVal); + void updateEvaluationResult(int sequence, bool success, const QVariant &bodyVal, const QVariant &refsVal); + void setCurrentFrameDetails(const QVariant &bodyVal, const QVariant &refsVal); + void updateScope(const QVariant &bodyVal, const QVariant &refsVal); + void createWatchDataList(const WatchItem *parent, const QVariantList &properties, const QVariant &refsVal); + void highlightExceptionCode(int lineNumber, const QString &filePath, const QString &errorMessage); + StackFrame extractStackFrame(const QVariant &bodyVal, const QVariant &refsVal); + + bool canEvaluateScript(const QString &script); + void updateScriptSource(const QString &fileName, int lineOffset, int columnOffset, const QString &source); + +public: + int sequence = -1; + QmlEngine *engine; + QHash breakpoints; + QHash breakpointsSync; + QList breakpointsTemp; + + QHash evaluatingExpression; + QHash localsAndWatchers; + QList updateLocalsAndWatchers; + QList debuggerCommands; + + //Cache + QList currentFrameScopes; + QHash stackIndexLookup; + + StepAction previousStepAction = Continue; + + QList sendBuffer; + + QHash sourceDocuments; + QHash > sourceEditors; + InteractiveInterpreter interpreter; + ApplicationLauncher applicationLauncher; + QmlInspectorAdapter inspectorAdapter; + QmlOutputParser outputParser; + + QTimer noDebugOutputTimer; + QHash pendingBreakpoints; + QList queryIds; + bool retryOnConnectFail = false; + bool automaticConnect = false; + + QTimer connectionTimer; + QmlDebug::QmlDebugConnection *connection; + QmlDebug::QDebugMessageClient *msgClient = 0; +}; + +static void updateDocument(IDocument *document, const QTextDocument *textDocument) +{ + if (auto baseTextDocument = qobject_cast(document)) + baseTextDocument->document()->setPlainText(textDocument->toPlainText()); } + /////////////////////////////////////////////////////////////////////// // // QmlEngine @@ -262,94 +226,97 @@ ConsoleManagerInterface *qmlConsoleManager() /////////////////////////////////////////////////////////////////////// QmlEngine::QmlEngine(const DebuggerRunParameters &startParameters, DebuggerEngine *masterEngine) - : DebuggerEngine(startParameters) - , m_adapter(this) - , m_inspectorAdapter(&m_adapter, this) - , m_retryOnConnectFail(false) - , m_automaticConnect(false) + : DebuggerEngine(startParameters), + d(new QmlEnginePrivate(this, new QmlDebugConnection(this))) { setObjectName(QLatin1String("QmlEngine")); if (masterEngine) setMasterEngine(masterEngine); - connect(&m_adapter, SIGNAL(connectionError(QDebugSupport::Error)), - SLOT(connectionError(QDebugSupport::Error))); - connect(&m_adapter, SIGNAL(serviceConnectionError(QString)), - SLOT(serviceConnectionError(QString))); - connect(&m_adapter, SIGNAL(connected()), - SLOT(connectionEstablished())); - connect(&m_adapter, SIGNAL(connectionStartupFailed()), - SLOT(connectionStartupFailed())); - - connect(stackHandler(), SIGNAL(stackChanged()), + connect(stackHandler(), &StackHandler::stackChanged, + this, &QmlEngine::updateCurrentContext); + connect(stackHandler(), &StackHandler::currentIndexChanged, + this, &QmlEngine::updateCurrentContext); + connect(inspectorView(), SIGNAL(currentIndexChanged(QModelIndex)), SLOT(updateCurrentContext())); - connect(stackHandler(), SIGNAL(currentIndexChanged()), - SLOT(updateCurrentContext())); - connect(inspectorTreeView(), SIGNAL(currentIndexChanged(QModelIndex)), - SLOT(updateCurrentContext())); - connect(m_inspectorAdapter.agent(), SIGNAL( - expressionResult(quint32,QVariant)), - SLOT(expressionEvaluated(quint32,QVariant))); - connect(m_adapter.messageClient(), - SIGNAL(message(QtMsgType,QString, - QmlDebug::QDebugContextInfo)), - SLOT(appendDebugOutput(QtMsgType,QString, - QmlDebug::QDebugContextInfo))); + connect(d->inspectorAdapter.agent(), &QmlInspectorAgent::expressionResult, + this, &QmlEngine::expressionEvaluated); + connect(&d->applicationLauncher, &ApplicationLauncher::processExited, + this, &QmlEngine::disconnected); + connect(&d->applicationLauncher, &ApplicationLauncher::appendMessage, + this, &QmlEngine::appendMessage); + connect(&d->applicationLauncher, &ApplicationLauncher::processStarted, + &d->noDebugOutputTimer, static_cast(&QTimer::start)); - connect(&m_applicationLauncher, - SIGNAL(processExited(int,QProcess::ExitStatus)), - SLOT(disconnected())); - connect(&m_applicationLauncher, - SIGNAL(appendMessage(QString,Utils::OutputFormat)), - SLOT(appendMessage(QString,Utils::OutputFormat))); - connect(&m_applicationLauncher, - SIGNAL(processStarted()), - &m_noDebugOutputTimer, - SLOT(start())); - - m_outputParser.setNoOutputText(ProjectExplorer::ApplicationLauncher - ::msgWinCannotRetrieveDebuggingOutput()); - connect(&m_outputParser, SIGNAL(waitingForConnectionOnPort(quint16)), - this, SLOT(beginConnection(quint16))); - connect(&m_outputParser, SIGNAL(noOutputMessage()), - this, SLOT(tryToConnect())); - connect(&m_outputParser, SIGNAL(errorMessage(QString)), - this, SLOT(appStartupFailed(QString))); + d->outputParser.setNoOutputText(ApplicationLauncher::msgWinCannotRetrieveDebuggingOutput()); + connect(&d->outputParser, &QmlOutputParser::waitingForConnectionOnPort, + this, &QmlEngine::beginConnection); + connect(&d->outputParser, &QmlOutputParser::noOutputMessage, + this, [this] { tryToConnect(); }); + connect(&d->outputParser, &QmlOutputParser::errorMessage, + this, &QmlEngine::appStartupFailed); // Only wait 8 seconds for the 'Waiting for connection' on application output, // then just try to connect (application output might be redirected / blocked) - m_noDebugOutputTimer.setSingleShot(true); - m_noDebugOutputTimer.setInterval(8000); - connect(&m_noDebugOutputTimer, SIGNAL(timeout()), this, SLOT(tryToConnect())); + d->noDebugOutputTimer.setSingleShot(true); + d->noDebugOutputTimer.setInterval(8000); + connect(&d->noDebugOutputTimer, SIGNAL(timeout()), this, SLOT(tryToConnect())); - ModelManagerInterface *mmIface = ModelManagerInterface::instance(); - if (mmIface) { - connect(ModelManagerInterface::instance(), SIGNAL(documentUpdated(QmlJS::Document::Ptr)), - this, SLOT(documentUpdated(QmlJS::Document::Ptr))); + if (auto mmIface = ModelManagerInterface::instance()) { + connect(mmIface, &ModelManagerInterface::documentUpdated, + this, &QmlEngine::documentUpdated); } // we won't get any debug output if (startParameters.useTerminal) { - m_noDebugOutputTimer.setInterval(0); - m_retryOnConnectFail = true; - m_automaticConnect = true; + d->noDebugOutputTimer.setInterval(0); + d->retryOnConnectFail = true; + d->automaticConnect = true; } - if (qmlConsoleManager()) - qmlConsoleManager()->setScriptEvaluator(this); + + if (auto consoleManager = ConsoleManagerInterface::instance()) + consoleManager->setScriptEvaluator(this); + + + d->connectionTimer.setInterval(4000); + d->connectionTimer.setSingleShot(true); + connect(&d->connectionTimer, &QTimer::timeout, + this, &QmlEngine::checkConnectionState); + + connect(d->connection, &QmlDebugConnection::stateMessage, + this, &QmlEngine::showConnectionStateMessage); + connect(d->connection, &QmlDebugConnection::errorMessage, + this, &QmlEngine::showConnectionErrorMessage); + connect(d->connection, &QmlDebugConnection::error, + this, &QmlEngine::connectionErrorOccurred); + connect(d->connection, &QmlDebugConnection::opened, + &d->connectionTimer, &QTimer::stop); + connect(d->connection, &QmlDebugConnection::opened, + this, &QmlEngine::connectionEstablished); + connect(d->connection, &QmlDebugConnection::closed, + this, &QmlEngine::disconnected); + + d->msgClient = new QDebugMessageClient(d->connection); + connect(d->msgClient, &QDebugMessageClient::newState, + this, &QmlEngine::clientStateChanged); + connect(d->msgClient, &QDebugMessageClient::message, + this, &appendDebugOutput); } QmlEngine::~QmlEngine() { - QSet documentsToClose; + QSet documentsToClose; - QHash >::iterator iter; - for (iter = m_sourceEditors.begin(); iter != m_sourceEditors.end(); ++iter) { - QWeakPointer textEditPtr = iter.value(); + QHash >::iterator iter; + for (iter = d->sourceEditors.begin(); iter != d->sourceEditors.end(); ++iter) { + QWeakPointer textEditPtr = iter.value(); if (textEditPtr) documentsToClose << textEditPtr.data()->document(); } - Core::EditorManager::closeDocuments(documentsToClose.toList()); + EditorManager::closeDocuments(documentsToClose.toList()); + + delete d; } void QmlEngine::setupInferior() @@ -358,7 +325,7 @@ void QmlEngine::setupInferior() notifyInferiorSetupOk(); - if (m_automaticConnect) + if (d->automaticConnect) beginConnection(); } @@ -373,7 +340,8 @@ void QmlEngine::connectionEstablished() if (!watchHandler()->watcherNames().isEmpty()) synchronizeWatchers(); - connect(watchModel(),SIGNAL(layoutChanged()),this,SLOT(synchronizeWatchers())); + connect(watchModel(), &QAbstractItemModel::layoutChanged, + this, &QmlEngine::synchronizeWatchers); if (state() == EngineRunRequested) notifyEngineRunAndInferiorRunOk(); @@ -382,13 +350,13 @@ void QmlEngine::connectionEstablished() void QmlEngine::tryToConnect(quint16 port) { showMessage(QLatin1String("QML Debugger: No application output received in time, trying to connect ..."), LogStatus); - m_retryOnConnectFail = true; + d->retryOnConnectFail = true; if (state() == EngineRunRequested) { if (isSlaveEngine()) { // Probably cpp is being debugged and hence we did not get the output yet. if (!masterEngine()->isDying()) { - m_noDebugOutputTimer.setInterval(4000); - m_noDebugOutputTimer.start(); + d->noDebugOutputTimer.setInterval(4000); + d->noDebugOutputTimer.start(); } else appStartupFailed(tr("No application output received in time")); @@ -396,15 +364,15 @@ void QmlEngine::tryToConnect(quint16 port) beginConnection(port); } } else { - m_automaticConnect = true; + d->automaticConnect = true; } } void QmlEngine::beginConnection(quint16 port) { - m_noDebugOutputTimer.stop(); + d->noDebugOutputTimer.stop(); - if (state() != EngineRunRequested && m_retryOnConnectFail) + if (state() != EngineRunRequested && d->retryOnConnectFail) return; QTC_ASSERT(state() == EngineRunRequested, return); @@ -428,19 +396,24 @@ void QmlEngine::beginConnection(quint16 port) if (runParameters().qmlServerPort > 0) port = runParameters().qmlServerPort; - m_adapter.beginConnectionTcp(host, port); -} + if (!d->connection || d->connection->isOpen()) + return; + d->connection->connectToHost(host, port); + + //A timeout to check the connection state + d->connectionTimer.start(); +} void QmlEngine::connectionStartupFailed() { - if (m_retryOnConnectFail) { + if (d->retryOnConnectFail) { // retry after 3 seconds ... QTimer::singleShot(3000, this, SLOT(beginConnection())); return; } - QMessageBox *infoBox = new QMessageBox(Core::ICore::mainWindow()); + QMessageBox *infoBox = new QMessageBox(ICore::mainWindow()); infoBox->setIcon(QMessageBox::Critical); infoBox->setWindowTitle(tr("Qt Creator")); infoBox->setText(tr("Could not connect to the in-process QML debugger." @@ -450,8 +423,8 @@ void QmlEngine::connectionStartupFailed() infoBox->setDefaultButton(QMessageBox::Retry); infoBox->setModal(true); - connect(infoBox, SIGNAL(finished(int)), - this, SLOT(errorMessageBoxFinished(int))); + connect(infoBox, &QDialog::finished, + this, &QmlEngine::errorMessageBoxFinished); infoBox->show(); } @@ -462,14 +435,14 @@ void QmlEngine::appStartupFailed(const QString &errorMessage) "\n%1").arg(errorMessage); if (isMasterEngine()) { - QMessageBox *infoBox = new QMessageBox(Core::ICore::mainWindow()); + QMessageBox *infoBox = new QMessageBox(ICore::mainWindow()); infoBox->setIcon(QMessageBox::Critical); infoBox->setWindowTitle(tr("Qt Creator")); infoBox->setText(error); infoBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Help); infoBox->setDefaultButton(QMessageBox::Ok); - connect(infoBox, SIGNAL(finished(int)), - this, SLOT(errorMessageBoxFinished(int))); + connect(infoBox, &QDialog::finished, + this, &QmlEngine::errorMessageBoxFinished); infoBox->show(); } else { showMessage(error, StatusBar); @@ -486,7 +459,7 @@ void QmlEngine::errorMessageBoxFinished(int result) break; } case QMessageBox::Help: { - Core::HelpManager::handleHelpRequest(QLatin1String("qthelp://org.qt-project.qtcreator/doc/creator-debugging-qml.html")); + HelpManager::handleHelpRequest(QLatin1String("qthelp://org.qt-project.qtcreator/doc/creator-debugging-qml.html")); // fall through } default: @@ -500,37 +473,15 @@ void QmlEngine::errorMessageBoxFinished(int result) } } -void QmlEngine::connectionError(QDebugSupport::Error error) +void QmlEngine::filterApplicationMessage(const QString &output, int /*channel*/) const { - if (error == QDebugSupport::RemoteClosedConnectionError) - showMessage(tr("QML Debugger: Remote host closed connection."), StatusBar); - - if (!isSlaveEngine()) { // normal flow for slave engine when gdb exits - notifyInferiorSpontaneousStop(); - notifyInferiorIll(); - } -} - -void QmlEngine::serviceConnectionError(const QString &serviceName) -{ - showMessage(tr("QML Debugger: Could not connect to service \"%1\".") - .arg(serviceName), StatusBar); -} - -bool QmlEngine::canDisplayTooltip() const -{ - return false; -} - -void QmlEngine::filterApplicationMessage(const QString &output, int /*channel*/) -{ - m_outputParser.processOutput(output); + d->outputParser.processOutput(output); } void QmlEngine::showMessage(const QString &msg, int channel, int timeout) const { if (channel == AppOutput || channel == AppError) - const_cast(this)->filterApplicationMessage(msg, channel); + filterApplicationMessage(msg, channel); DebuggerEngine::showMessage(msg, channel, timeout); } @@ -539,25 +490,23 @@ void QmlEngine::gotoLocation(const Location &location) const QString fileName = location.fileName(); if (QUrl(fileName).isLocalFile()) { // internal file from source files -> show generated .js - QTC_ASSERT(m_sourceDocuments.contains(fileName), return); + QTC_ASSERT(d->sourceDocuments.contains(fileName), return); QString titlePattern = tr("JS Source for %1").arg(fileName); //Check if there are open documents with the same title - foreach (Core::IDocument *document, Core::DocumentModel::openedDocuments()) { + foreach (IDocument *document, DocumentModel::openedDocuments()) { if (document->displayName() == titlePattern) { - Core::EditorManager::activateEditorForDocument(document); + EditorManager::activateEditorForDocument(document); return; } } - Core::IEditor *editor = Core::EditorManager::openEditorWithContents( + IEditor *editor = EditorManager::openEditorWithContents( QmlJSEditor::Constants::C_QMLJSEDITOR_ID, &titlePattern); if (editor) { editor->document()->setProperty(Constants::OPENED_BY_DEBUGGER, true); - QPlainTextEdit *plainTextEdit = - qobject_cast(editor->widget()); - if (plainTextEdit) + if (auto plainTextEdit = qobject_cast(editor->widget())) plainTextEdit->setReadOnly(true); - updateDocument(editor->document(), m_sourceDocuments.value(fileName)); + updateDocument(editor->document(), d->sourceDocuments.value(fileName)); } } else { DebuggerEngine::gotoLocation(location); @@ -566,8 +515,15 @@ void QmlEngine::gotoLocation(const Location &location) void QmlEngine::closeConnection() { - disconnect(watchModel(),SIGNAL(layoutChanged()),this,SLOT(synchronizeWatchers())); - m_adapter.closeConnection(); + disconnect(watchModel(), &QAbstractItemModel::layoutChanged, + this, &QmlEngine::synchronizeWatchers); + + if (d->connectionTimer.isActive()) { + d->connectionTimer.stop(); + } else { + if (d->connection) + d->connection->close(); + } } void QmlEngine::runEngine() @@ -576,25 +532,25 @@ void QmlEngine::runEngine() if (!isSlaveEngine()) { if (runParameters().startMode == AttachToRemoteServer) - m_noDebugOutputTimer.start(); + d->noDebugOutputTimer.start(); else if (runParameters().startMode == AttachToRemoteProcess) beginConnection(); else startApplicationLauncher(); } else { - m_noDebugOutputTimer.start(); + d->noDebugOutputTimer.start(); } } void QmlEngine::startApplicationLauncher() { - if (!m_applicationLauncher.isRunning()) { + if (!d->applicationLauncher.isRunning()) { appendMessage(tr("Starting %1 %2").arg( QDir::toNativeSeparators(runParameters().executable), runParameters().processArgs) + QLatin1Char('\n') , Utils::NormalMessageFormat); - m_applicationLauncher.start(ProjectExplorer::ApplicationLauncher::Gui, + d->applicationLauncher.start(ApplicationLauncher::Gui, runParameters().executable, runParameters().processArgs); } @@ -602,10 +558,10 @@ void QmlEngine::startApplicationLauncher() void QmlEngine::stopApplicationLauncher() { - if (m_applicationLauncher.isRunning()) { - disconnect(&m_applicationLauncher, SIGNAL(processExited(int,QProcess::ExitStatus)), - this, SLOT(disconnected())); - m_applicationLauncher.stop(); + if (d->applicationLauncher.isRunning()) { + disconnect(&d->applicationLauncher, &ApplicationLauncher::processExited, + this, &QmlEngine::disconnected); + d->applicationLauncher.stop(); } } @@ -622,12 +578,12 @@ void QmlEngine::notifyEngineRemoteSetupFinished(const RemoteSetupResult &result) // The remote setup can take while especialy with mixed debugging. // Just waiting for 8 seconds is not enough. Increase the timeout // to 60 s - // In case we get an output the m_outputParser will start the connection. - m_noDebugOutputTimer.setInterval(60000); + // In case we get an output the d->outputParser will start the connection. + d->noDebugOutputTimer.setInterval(60000); } else { if (isMasterEngine()) - QMessageBox::critical(Core::ICore::dialogParent(), tr("Failed to start application"), + QMessageBox::critical(ICore::dialogParent(), tr("Failed to start application"), tr("Application startup failed: %1").arg(result.reason)); notifyEngineSetupFailed(); } @@ -646,16 +602,15 @@ void QmlEngine::notifyEngineRemoteServerRunning(const QByteArray &serverChannel, notifyEngineSetupOk(); // The remote setup can take a while especially with mixed debugging. - // Just waiting for 8 seconds is not enough. Increase the timeout - // to 60 s - // In case we get an output the m_outputParser will start the connection. - m_noDebugOutputTimer.setInterval(60000); + // Just waiting for 8 seconds is not enough. Increase the timeout to 60 s. + // In case we get an output the d->outputParser will start the connection. + d->noDebugOutputTimer.setInterval(60000); } void QmlEngine::shutdownInferior() { - if (m_adapter.activeDebuggerClient()) - m_adapter.activeDebuggerClient()->endSession(); + // End session. + d->disconnect(); if (isSlaveEngine()) resetLocation(); @@ -667,12 +622,11 @@ void QmlEngine::shutdownInferior() void QmlEngine::shutdownEngine() { - if (m_adapter.activeDebuggerClient()) - m_adapter.activeDebuggerClient()->resetSession(); + clearExceptionSelection(); - if (qmlConsoleManager()) - qmlConsoleManager()->setScriptEvaluator(0); - m_noDebugOutputTimer.stop(); + if (auto consoleManager = ConsoleManagerInterface::instance()) + consoleManager->setScriptEvaluator(0); + d->noDebugOutputTimer.stop(); // double check (ill engine?): stopApplicationLauncher(); @@ -688,12 +642,12 @@ void QmlEngine::setupEngine() // we need to get the port first notifyEngineRequestRemoteSetup(); } else { - m_applicationLauncher.setEnvironment(runParameters().environment); - m_applicationLauncher.setWorkingDirectory(runParameters().workingDirectory); + d->applicationLauncher.setEnvironment(runParameters().environment); + d->applicationLauncher.setWorkingDirectory(runParameters().workingDirectory); // We can't do this in the constructore because runControl() isn't yet defined - connect(&m_applicationLauncher, SIGNAL(bringToForegroundRequested(qint64)), - runControl(), SLOT(bringApplicationToForeground(qint64)), + connect(&d->applicationLauncher, &ApplicationLauncher::bringToForegroundRequested, + runControl(), &RunControl::bringApplicationToForeground, Qt::UniqueConnection); notifyEngineSetupOk(); @@ -703,8 +657,8 @@ void QmlEngine::setupEngine() void QmlEngine::continueInferior() { QTC_ASSERT(state() == InferiorStopOk, qDebug() << state()); - if (m_adapter.activeDebuggerClient()) - m_adapter.activeDebuggerClient()->continueInferior(); + clearExceptionSelection(); + d->continueDebugging(Continue); resetLocation(); notifyInferiorRunRequested(); notifyInferiorRunOk(); @@ -712,39 +666,39 @@ void QmlEngine::continueInferior() void QmlEngine::interruptInferior() { - if (m_adapter.activeDebuggerClient()) - m_adapter.activeDebuggerClient()->interruptInferior(); + showMessage(_(INTERRUPT), LogInput); + d->sendMessage(d->packMessage(INTERRUPT)); notifyInferiorStopOk(); } void QmlEngine::executeStep() { - if (m_adapter.activeDebuggerClient()) - m_adapter.activeDebuggerClient()->executeStep(); + clearExceptionSelection(); + d->continueDebugging(StepIn); notifyInferiorRunRequested(); notifyInferiorRunOk(); } void QmlEngine::executeStepI() { - if (m_adapter.activeDebuggerClient()) - m_adapter.activeDebuggerClient()->executeStepI(); + clearExceptionSelection(); + d->continueDebugging(StepIn); notifyInferiorRunRequested(); notifyInferiorRunOk(); } void QmlEngine::executeStepOut() { - if (m_adapter.activeDebuggerClient()) - m_adapter.activeDebuggerClient()->executeStepOut(); + clearExceptionSelection(); + d->continueDebugging(StepOut); notifyInferiorRunRequested(); notifyInferiorRunOk(); } void QmlEngine::executeNext() { - if (m_adapter.activeDebuggerClient()) - m_adapter.activeDebuggerClient()->executeNext(); + clearExceptionSelection(); + d->continueDebugging(Next); notifyInferiorRunRequested(); notifyInferiorRunOk(); } @@ -765,8 +719,11 @@ void QmlEngine::executeRunToLine(const ContextData &data) bool valid; if (adjustBreakpointLineAndColumn(data.fileName, &line, &column, &valid)) modifiedData.lineNumber = line; - if (m_adapter.activeDebuggerClient()) - m_adapter.activeDebuggerClient()->executeRunToLine(modifiedData); + d->setBreakpoint(QString(_(SCRIPTREGEXP)), modifiedData.fileName, + true, modifiedData.lineNumber); + clearExceptionSelection(); + d->continueDebugging(Continue); + notifyInferiorRunRequested(); notifyInferiorRunOk(); } @@ -788,8 +745,10 @@ void QmlEngine::activateFrame(int index) if (state() != InferiorStopOk && state() != InferiorUnrunnable) return; - if (m_adapter.activeDebuggerClient()) - m_adapter.activeDebuggerClient()->activateFrame(index); + if (index != stackHandler()->currentIndex()) + d->frame(d->stackIndexLookup.value(index)); + + stackHandler()->setCurrentIndex(index); gotoLocation(stackHandler()->frames().value(index)); } @@ -811,31 +770,39 @@ void QmlEngine::insertBreakpoint(Breakpoint bp) bool valid = false; if (!adjustBreakpointLineAndColumn(params.fileName, &line, &column, &valid)) { - pendingBreakpoints.insertMulti(params.fileName, bp); + d->pendingBreakpoints.insertMulti(params.fileName, bp); return; } if (!valid) return; } - if (m_adapter.activeDebuggerClient()) { - m_adapter.activeDebuggerClient()->insertBreakpoint(bp, line, column); - } else { - foreach (QmlV8DebuggerClient *client, m_adapter.debuggerClients()) { - client->insertBreakpoint(bp, line, column); - } + if (params.type == BreakpointAtJavaScriptThrow) { + bp.notifyBreakpointInsertOk(); + d->setExceptionBreak(AllExceptions, params.enabled); + + } else if (params.type == BreakpointByFileAndLine) { + d->setBreakpoint(QString(_(SCRIPTREGEXP)), params.fileName, + params.enabled, line, column, + QLatin1String(params.condition), params.ignoreCount); + + } else if (params.type == BreakpointOnQmlSignalEmit) { + d->setBreakpoint(QString(_(EVENT)), params.functionName, params.enabled); + bp.notifyBreakpointInsertOk(); } + + d->breakpointsSync.insert(d->sequence, bp.id()); } void QmlEngine::removeBreakpoint(Breakpoint bp) { const BreakpointParameters ¶ms = bp.parameters(); if (params.type == BreakpointByFileAndLine && - pendingBreakpoints.contains(params.fileName)) { - auto it = pendingBreakpoints.find(params.fileName); - while (it != pendingBreakpoints.end() && it.key() == params.fileName) { + d->pendingBreakpoints.contains(params.fileName)) { + auto it = d->pendingBreakpoints.find(params.fileName); + while (it != d->pendingBreakpoints.end() && it.key() == params.fileName) { if (it.value() == bp.id()) { - pendingBreakpoints.erase(it); + d->pendingBreakpoints.erase(it); return; } ++it; @@ -846,13 +813,15 @@ void QmlEngine::removeBreakpoint(Breakpoint bp) QTC_ASSERT(state == BreakpointRemoveRequested, qDebug() << bp << this << state); bp.notifyBreakpointRemoveProceeding(); - if (m_adapter.activeDebuggerClient()) { - m_adapter.activeDebuggerClient()->removeBreakpoint(bp); - } else { - foreach (QmlV8DebuggerClient *client, m_adapter.debuggerClients()) { - client->removeBreakpoint(bp); - } - } + int breakpoint = d->breakpoints.value(bp.id()); + d->breakpoints.remove(bp.id()); + + if (params.type == BreakpointAtJavaScriptThrow) + d->setExceptionBreak(AllExceptions); + else if (params.type == BreakpointOnQmlSignalEmit) + d->setBreakpoint(QString(_(EVENT)), params.functionName, false); + else + d->clearBreakpoint(breakpoint); if (bp.state() == BreakpointRemoveProceeding) bp.notifyBreakpointRemoveOk(); @@ -864,12 +833,24 @@ void QmlEngine::changeBreakpoint(Breakpoint bp) QTC_ASSERT(state == BreakpointChangeRequested, qDebug() << bp << this << state); bp.notifyBreakpointChangeProceeding(); - if (m_adapter.activeDebuggerClient()) { - m_adapter.activeDebuggerClient()->changeBreakpoint(bp); + const BreakpointParameters ¶ms = bp.parameters(); + + BreakpointResponse br = bp.response(); + if (params.type == BreakpointAtJavaScriptThrow) { + d->setExceptionBreak(AllExceptions, params.enabled); + br.enabled = params.enabled; + bp.setResponse(br); + } else if (params.type == BreakpointOnQmlSignalEmit) { + d->setBreakpoint(QString(_(EVENT)), params.functionName, params.enabled); + br.enabled = params.enabled; + bp.setResponse(br); } else { - foreach (QmlV8DebuggerClient *client, m_adapter.debuggerClients()) { - client->changeBreakpoint(bp); - } + //V8 supports only minimalistic changes in breakpoint + //Remove the breakpoint and add again + bp.notifyBreakpointChangeOk(); + bp.removeBreakpoint(); + BreakHandler *handler = d->engine->breakHandler(); + handler->appendBreakpoint(params); } if (bp.state() == BreakpointChangeProceeding) @@ -918,14 +899,6 @@ void QmlEngine::attemptBreakpointSynchronization() } DebuggerEngine::attemptBreakpointSynchronization(); - - if (m_adapter.activeDebuggerClient()) { - m_adapter.activeDebuggerClient()->synchronizeBreakpoints(); - } else { - foreach (QmlV8DebuggerClient *client, m_adapter.debuggerClients()) { - client->synchronizeBreakpoints(); - } - } } bool QmlEngine::acceptsBreakpoint(Breakpoint bp) const @@ -937,10 +910,10 @@ bool QmlEngine::acceptsBreakpoint(Breakpoint bp) const //TODO: enable setting of breakpoints before start of debug session //For now, the event breakpoint can be set after the activeDebuggerClient is known //This is because the older client does not support BreakpointOnQmlSignalHandler - bool acceptBreakpoint = false; - if (m_adapter.activeDebuggerClient()) - acceptBreakpoint = m_adapter.activeDebuggerClient()->acceptsBreakpoint(bp); - return acceptBreakpoint; + BreakpointType type = bp.type(); + return type == BreakpointOnQmlSignalEmit + || type == BreakpointByFileAndLine + || type == BreakpointAtJavaScriptThrow; } void QmlEngine::loadSymbols(const QString &moduleName) @@ -958,8 +931,7 @@ void QmlEngine::reloadModules() void QmlEngine::reloadSourceFiles() { - if (m_adapter.activeDebuggerClient()) - m_adapter.activeDebuggerClient()->getSourceFiles(); + d->scripts(4, QList(), true, QVariant()); } void QmlEngine::requestModuleSymbols(const QString &moduleName) @@ -967,12 +939,6 @@ void QmlEngine::requestModuleSymbols(const QString &moduleName) Q_UNUSED(moduleName) } -////////////////////////////////////////////////////////////////////// -// -// Tooltip specific stuff -// -////////////////////////////////////////////////////////////////////// - bool QmlEngine::canHandleToolTip(const DebuggerToolTipContext &) const { // This is processed by QML inspector, which has dependencies to @@ -981,20 +947,23 @@ bool QmlEngine::canHandleToolTip(const DebuggerToolTipContext &) const return true; } -////////////////////////////////////////////////////////////////////// -// -// Watch specific stuff -// -////////////////////////////////////////////////////////////////////// - void QmlEngine::assignValueInDebugger(WatchItem *item, const QString &expression, const QVariant &valueV) { if (!expression.isEmpty()) { - if (item->isInspect() && m_inspectorAdapter.agent()) - m_inspectorAdapter.agent()->assignValue(item, expression, valueV); - else if (m_adapter.activeDebuggerClient()) - m_adapter.activeDebuggerClient()->assignValueInDebugger(item, expression, valueV); + if (item->isInspect() && d->inspectorAdapter.agent()) { + d->inspectorAdapter.agent()->assignValue(item, expression, valueV); + } else { + StackHandler *handler = stackHandler(); + QString expression = QString(_("%1 = %2;")).arg(expression).arg(valueV.toString()); + if (handler->isContentsValid() && handler->currentFrame().isUsable()) { + d->evaluate(expression, false, false, handler->currentIndex()); + d->updateLocalsAndWatchers.append(d->sequence); + } else { + showMessage(QString(_("Cannot evaluate %1 in current stack frame")).arg( + expression), ConsoleOutput); + } + } } } @@ -1008,14 +977,11 @@ void QmlEngine::updateWatchData(const QByteArray &iname) return; if (item->isInspect()) { - m_inspectorAdapter.agent()->updateWatchData(*item); + d->inspectorAdapter.agent()->updateWatchData(*item); } else { - if (!item->name.isEmpty() && m_adapter.activeDebuggerClient()) { - if (item->isValueNeeded()) - m_adapter.activeDebuggerClient()->updateWatchData(*item); - if (item->isChildrenNeeded() && watchHandler()->isExpandedIName(item->iname)) { - m_adapter.activeDebuggerClient()->expandObject(item->iname, item->id); - } + if (!item->name.isEmpty()) { + if (item->isChildrenNeeded() && watchHandler()->isExpandedIName(item->iname)) + d->expandObject(item->iname, item->id); } synchronizeWatchers(); } @@ -1025,26 +991,30 @@ void QmlEngine::selectWatchData(const QByteArray &iname) { const WatchItem *item = watchHandler()->findItem(iname); if (item && item->isInspect()) - m_inspectorAdapter.agent()->watchDataSelected(item->id); + d->inspectorAdapter.agent()->watchDataSelected(item->id); } void QmlEngine::synchronizeWatchers() { - QStringList watchedExpressions = watchHandler()->watchedExpressions(); + if (state() != InferiorStopOk) + return; + + QStringList watchers = watchHandler()->watchedExpressions(); + // send watchers list - if (m_adapter.activeDebuggerClient()) { - m_adapter.activeDebuggerClient()->synchronizeWatchers(watchedExpressions); - } else { - foreach (QmlV8DebuggerClient *client, m_adapter.debuggerClients()) - client->synchronizeWatchers(watchedExpressions); + foreach (const QString &exp, watchers) { + StackHandler *handler = stackHandler(); + if (handler->isContentsValid() && handler->currentFrame().isUsable()) { + d->evaluate(exp, false, false, handler->currentIndex()); + d->evaluatingExpression.insert(d->sequence, exp); + } } } -ConsoleItem *constructLogItemTree(ConsoleItem *parent, - const QVariant &result, - const QString &key = QString()) +static ConsoleItem *constructLogItemTree(ConsoleItem *parent, + const QVariant &result, + const QString &key = QString()) { - using namespace QmlJS; bool sorted = boolSetting(SortStructMembers); if (!result.isValid()) return 0; @@ -1086,13 +1056,10 @@ ConsoleItem *constructLogItemTree(ConsoleItem *parent, void QmlEngine::expressionEvaluated(quint32 queryId, const QVariant &result) { - if (queryIds.contains(queryId)) { - queryIds.removeOne(queryId); - using namespace QmlJS; - ConsoleManagerInterface *consoleManager = qmlConsoleManager(); - if (consoleManager) { - ConsoleItem *item = constructLogItemTree(consoleManager->rootItem(), result); - if (item) + if (d->queryIds.contains(queryId)) { + d->queryIds.removeOne(queryId); + if (auto consoleManager = ConsoleManagerInterface::instance()) { + if (ConsoleItem *item = constructLogItemTree(consoleManager->rootItem(), result)) consoleManager->printToConsolePane(item); } } @@ -1116,18 +1083,12 @@ bool QmlEngine::hasCapability(unsigned cap) const void QmlEngine::quitDebugger() { - m_noDebugOutputTimer.stop(); - m_automaticConnect = false; - m_retryOnConnectFail = false; + d->noDebugOutputTimer.stop(); + d->automaticConnect = false; + d->retryOnConnectFail = false; DebuggerEngine::quitDebugger(); } -void QmlEngine::inferiorSpontaneousStop() -{ - if (state() == InferiorRunOk) - notifyInferiorSpontaneousStop(); -} - void QmlEngine::disconnected() { showMessage(tr("QML Debugger disconnected."), StatusBar); @@ -1137,9 +1098,9 @@ void QmlEngine::disconnected() void QmlEngine::documentUpdated(Document::Ptr doc) { QString fileName = doc->fileName(); - if (pendingBreakpoints.contains(fileName)) { - QList bps = pendingBreakpoints.values(fileName); - pendingBreakpoints.remove(fileName); + if (d->pendingBreakpoints.contains(fileName)) { + QList bps = d->pendingBreakpoints.values(fileName); + d->pendingBreakpoints.remove(fileName); foreach (const Breakpoint bp, bps) insertBreakpoint(bp); } @@ -1151,13 +1112,12 @@ void QmlEngine::updateCurrentContext() if (state() == InferiorStopOk) { context = stackHandler()->currentFrame().function; } else { - QModelIndex currentIndex = inspectorTreeView()->currentIndex(); + QModelIndex currentIndex = inspectorView()->currentIndex(); const WatchData *currentData = watchHandler()->watchItem(currentIndex); if (!currentData) return; const WatchData *parentData = watchHandler()->watchItem(currentIndex.parent()); - const WatchData *grandParentData = watchHandler()->watchItem( - currentIndex.parent().parent()); + const WatchData *grandParentData = watchHandler()->watchItem(currentIndex.parent().parent()); if (currentData->id != parentData->id) context = currentData->name; else if (parentData->id != grandParentData->id) @@ -1168,44 +1128,24 @@ void QmlEngine::updateCurrentContext() synchronizeWatchers(); - ConsoleManagerInterface *consoleManager = qmlConsoleManager(); - if (consoleManager) + if (auto consoleManager = ConsoleManagerInterface::instance()) consoleManager->setContext(tr("Context:") + QLatin1Char(' ') + context); } -void QmlEngine::appendDebugOutput(QtMsgType type, const QString &message, - const QmlDebug::QDebugContextInfo &info) -{ - using namespace QmlJS; - ConsoleItem::ItemType itemType; - switch (type) { - case QtDebugMsg: - itemType = ConsoleItem::DebugType; - break; - case QtWarningMsg: - itemType = ConsoleItem::WarningType; - break; - case QtCriticalMsg: - case QtFatalMsg: - itemType = ConsoleItem::ErrorType; - break; - default: - //This case is not possible - return; - } - ConsoleManagerInterface *consoleManager = qmlConsoleManager(); - if (consoleManager) { - ConsoleItem *item = new ConsoleItem(consoleManager->rootItem(), itemType, message); - item->file = info.file; - item->line = info.line; - consoleManager->printToConsolePane(item); - } -} - void QmlEngine::executeDebuggerCommand(const QString &command, DebuggerLanguages languages) { - if ((languages & QmlLanguage) && m_adapter.activeDebuggerClient()) - m_adapter.activeDebuggerClient()->executeDebuggerCommand(command); + if (!(languages & QmlLanguage)) + return; + + StackHandler *handler = stackHandler(); + if (handler->isContentsValid() && handler->currentFrame().isUsable()) { + d->evaluate(command, false, false, handler->currentIndex()); + d->debuggerCommands.append(d->sequence); + } else { + //Currently cannot evaluate if not in a javascript break + d->engine->showMessage(QString(_("Cannot evaluate %1 in current stack frame")).arg( + command), ConsoleOutput); + } } bool QmlEngine::evaluateScript(const QString &expression) @@ -1214,17 +1154,15 @@ bool QmlEngine::evaluateScript(const QString &expression) // Evaluate expression based on engine state // When engine->state() == InferiorStopOk, the expression is sent to debuggerClient. if (state() != InferiorStopOk) { - QModelIndex currentIndex = inspectorTreeView()->currentIndex(); - QmlInspectorAgent *agent = m_inspectorAdapter.agent(); + QModelIndex currentIndex = inspectorView()->currentIndex(); + QmlInspectorAgent *agent = d->inspectorAdapter.agent(); quint32 queryId = agent->queryExpressionResult(watchHandler()->watchItem(currentIndex)->id, expression); if (queryId) { - queryIds << queryId; + d->queryIds.append(queryId); } else { didEvaluate = false; - using namespace QmlJS; - ConsoleManagerInterface *consoleManager = qmlConsoleManager(); - if (consoleManager) { + if (auto consoleManager = ConsoleManagerInterface::instance()) { consoleManager->printToConsolePane(ConsoleItem::ErrorType, _("Error evaluating expression.")); } @@ -1235,42 +1173,15 @@ bool QmlEngine::evaluateScript(const QString &expression) return didEvaluate; } -QString QmlEngine::qmlImportPath() const -{ - return runParameters().environment.value(QLatin1String("QML_IMPORT_PATH")); -} - -void QmlEngine::logMessage(const QString &service, LogDirection direction, const QString &message) -{ - QString msg = service; - msg += direction == LogSend ? QLatin1String(": sending ") : QLatin1String(": receiving "); - msg += message; - showMessage(msg, LogDebug); -} - -void QmlEngine::setSourceFiles(const QStringList &fileNames) -{ - QMap files; - foreach (const QString &file, fileNames) { - QString shortName = file; - QString fullName = toFileInProject(file); - files.insert(shortName, fullName); - } - - sourceFilesHandler()->setSourceFiles(files); - //update open editors - -} - -void QmlEngine::updateScriptSource(const QString &fileName, int lineOffset, int columnOffset, - const QString &source) +void QmlEnginePrivate::updateScriptSource(const QString &fileName, int lineOffset, int columnOffset, + const QString &source) { QTextDocument *document = 0; - if (m_sourceDocuments.contains(fileName)) { - document = m_sourceDocuments.value(fileName); + if (sourceDocuments.contains(fileName)) { + document = sourceDocuments.value(fileName); } else { document = new QTextDocument(this); - m_sourceDocuments.insert(fileName, document); + sourceDocuments.insert(fileName, document); } // We're getting an unordered set of snippets that can even interleave @@ -1307,7 +1218,7 @@ void QmlEngine::updateScriptSource(const QString &fileName, int lineOffset, int //update open editors QString titlePattern = tr("JS Source for %1").arg(fileName); //Check if there are open editors with the same title - foreach (Core::IDocument *doc, Core::DocumentModel::openedDocuments()) { + foreach (IDocument *doc, DocumentModel::openedDocuments()) { if (doc->displayName() == titlePattern) { updateDocument(doc, document); break; @@ -1315,45 +1226,1488 @@ void QmlEngine::updateScriptSource(const QString &fileName, int lineOffset, int } } -void QmlEngine::updateDocument(Core::IDocument *document, const QTextDocument *textDocument) +bool QmlEnginePrivate::canEvaluateScript(const QString &script) { - TextEditor::TextDocument *baseTextDocument - = qobject_cast(document); - if (!baseTextDocument) - return; - - baseTextDocument->document()->setPlainText(textDocument->toPlainText()); + interpreter.clearText(); + interpreter.appendText(script); + return interpreter.canEvaluate(); } -bool QmlEngine::canEvaluateScript(const QString &script) +void QmlEngine::connectionErrorOccurred(QDebugSupport::Error error) { - m_interpreter.clearText(); - m_interpreter.appendText(script); - return m_interpreter.canEvaluate(); + // this is only an error if we are already connected and something goes wrong. + if (isConnected()) { + if (error == QDebugSupport::RemoteClosedConnectionError) + showMessage(tr("QML Debugger: Remote host closed connection."), StatusBar); + + if (!isSlaveEngine()) { // normal flow for slave engine when gdb exits + notifyInferiorSpontaneousStop(); + notifyInferiorIll(); + } + } else { + d->connectionTimer.stop(); + connectionStartupFailed(); + } } -bool QmlEngine::adjustBreakpointLineAndColumn( - const QString &filePath, quint32 *line, quint32 *column, bool *valid) +void QmlEngine::clientStateChanged(QmlDebugClient::State state) { - bool success = false; - //check if file is in the latest snapshot - //ignoring documentChangedOnDisk - //TODO:: update breakpoints if document is changed. - ModelManagerInterface *mmIface = ModelManagerInterface::instance(); - if (mmIface) { - Document::Ptr doc = mmIface->newestSnapshot(). - document(filePath); - if (doc.isNull()) { - ModelManagerInterface::instance()->updateSourceFiles( - QStringList() << filePath, false); - } else { - ASTWalker walker; - walker(doc->ast(), line, column); - *valid = walker.done; - success = true; + QString serviceName; + float version = 0; + if (QmlDebugClient *client = qobject_cast(sender())) { + serviceName = client->name(); + version = client->remoteVersion(); + } + + logServiceStateChange(serviceName, version, state); +} + +void QmlEngine::checkConnectionState() +{ + if (!isConnected()) { + closeConnection(); + connectionStartupFailed(); + } +} + +bool QmlEngine::isConnected() const +{ + return d->connection->isOpen(); +} + +void QmlEngine::showConnectionStateMessage(const QString &message) +{ + showMessage(_("QML Debugger: ") + message, LogStatus); +} + +void QmlEngine::showConnectionErrorMessage(const QString &message) +{ + showMessage(_("QML Debugger: ") + message, LogError); +} + +void QmlEngine::logServiceStateChange(const QString &service, float version, + QmlDebugClient::State newState) +{ + switch (newState) { + case QmlDebugClient::Unavailable: { + showConnectionStateMessage(_("Status of \"%1\" Version: %2 changed to 'unavailable'."). + arg(service).arg(QString::number(version))); + break; + } + case QmlDebugClient::Enabled: { + showConnectionStateMessage(_("Status of \"%1\" Version: %2 changed to 'enabled'."). + arg(service).arg(QString::number(version))); + break; + } + + case QmlDebugClient::NotConnected: { + showConnectionStateMessage(_("Status of \"%1\" Version: %2 changed to 'not connected'."). + arg(service).arg(QString::number(version))); + break; + } + } +} + +void QmlEngine::logServiceActivity(const QString &service, const QString &logMessage) +{ + showMessage(service + QLatin1Char(' ') + logMessage, LogDebug); +} + +void QmlEnginePrivate::connect() +{ + engine->showMessage(_(CONNECT), LogInput); + sendMessage(packMessage(CONNECT)); +} + +void QmlEnginePrivate::disconnect() +{ + // { "seq" : , + // "type" : "request", + // "command" : "disconnect", + // } + QJsonObject jsonVal = initObject(); + jsonVal.insert(_(COMMAND), _(DISCONNECT)); + + const QByteArray msg = QJsonDocument(jsonVal).toJson(QJsonDocument::Compact); + engine->showMessage(QString::fromUtf8(msg), LogInput); + sendMessage(packMessage(DISCONNECT, msg)); +} + +void QmlEnginePrivate::continueDebugging(StepAction action) +{ + // { "seq" : , + // "type" : "request", + // "command" : "continue", + // "arguments" : { "stepaction" : <"in", "next" or "out">, + // "stepcount" : + // } + // } + QJsonObject jsonVal = initObject(); + jsonVal.insert(_(COMMAND), _(CONTINEDEBUGGING)); + + if (action != Continue) { + QJsonObject args; + switch (action) { + case StepIn: + args.insert(_(STEPACTION), _(IN)); + break; + case StepOut: + args.insert(_(STEPACTION), _(OUT)); + break; + case Next: + args.insert(_(STEPACTION), _(NEXT)); + break; + default:break; + } + + jsonVal.insert(_(ARGUMENTS), args); + } + sendAndLogV8Request(jsonVal); + previousStepAction = action; +} + +void QmlEnginePrivate::evaluate(const QString expr, bool global, + bool disableBreak, int frame, bool addContext) +{ + // { "seq" : , + // "type" : "request", + // "command" : "evaluate", + // "arguments" : { "expression" : , + // "frame" : , + // "global" : , + // "disable_break" : , + // "additional_context" : [ + // { "name" : , "handle" : }, + // { "name" : , "handle" : }, + // ... + // ] + // } + // } + QJsonObject jsonVal = initObject(); + jsonVal.insert(_(COMMAND), _(EVALUATE)); + + QJsonObject args { + { _(EXPRESSION), expr } + }; + + if (frame != -1) + args.insert(_(FRAME), frame); + + if (global) + args.insert(_(GLOBAL), global); + + if (disableBreak) + args.insert(_(DISABLE_BREAK), disableBreak); + + if (addContext) { + WatchHandler *watchHandler = engine->watchHandler(); + QAbstractItemModel *watchModel = watchHandler->model(); + int rowCount = watchModel->rowCount(); + + QJsonArray ctxtList; + while (rowCount) { + QModelIndex index = watchModel->index(--rowCount, 0); + const WatchData *data = watchHandler->watchItem(index); + const QJsonObject ctxt { + { _(NAME), data->name }, + { _(HANDLE), int(data->id) } + }; + + ctxtList.push_front(ctxt); + } + + args.insert(_(ADDITIONAL_CONTEXT), ctxtList); + } + + jsonVal.insert(_(ARGUMENTS), args); + + sendAndLogV8Request(jsonVal); +} + +void QmlEnginePrivate::lookup(QList handles, bool includeSource) +{ + // { "seq" : , + // "type" : "request", + // "command" : "lookup", + // "arguments" : { "handles" : , + // "includeSource" : , + // } + // } + QJsonObject jsonVal = initObject(); + jsonVal.insert(_(COMMAND), _(LOOKUP)); + + QJsonObject args; + + QJsonArray array; + foreach (int handle, handles) + array.push_back(handle); + args.insert(_(HANDLES), array); + + if (includeSource) + args.insert(_(INCLUDESOURCE), includeSource); + + jsonVal.insert(_(ARGUMENTS), args); + + sendAndLogV8Request(jsonVal); +} + +void QmlEnginePrivate::backtrace(int fromFrame, int toFrame, bool bottom) +{ + // { "seq" : , + // "type" : "request", + // "command" : "backtrace", + // "arguments" : { "fromFrame" : + // "toFrame" : + // "bottom" : + // } + // } + QJsonObject jsonVal = initObject(); + jsonVal.insert(_(COMMAND), _(BACKTRACE)); + + QJsonObject args; + + if (fromFrame != -1) + args.insert(_(FROMFRAME), fromFrame); + + if (toFrame != -1) + args.insert(_(TOFRAME), toFrame); + + if (bottom) + args.insert(_(BOTTOM), bottom); + + jsonVal.insert(_(ARGUMENTS), args); + + sendAndLogV8Request(jsonVal); +} + +void QmlEnginePrivate::frame(int number) +{ + // { "seq" : , + // "type" : "request", + // "command" : "frame", + // "arguments" : { "number" : + // } + // } + QJsonObject jsonVal = initObject(); + jsonVal.insert(_(COMMAND), _(FRAME)); + + if (number != -1) { + const QJsonObject args { + { _(NUMBER), number } + }; + + jsonVal.insert(_(ARGUMENTS), args); + } + + sendAndLogV8Request(jsonVal); +} + +void QmlEnginePrivate::scope(int number, int frameNumber) +{ + // { "seq" : , + // "type" : "request", + // "command" : "scope", + // "arguments" : { "number" : + // "frameNumber" : + // } + // } + QJsonObject jsonVal = initObject(); + jsonVal.insert(_(COMMAND), _(SCOPE)); + + if (number != -1) { + QJsonObject args { + { _(NUMBER), number } + }; + + if (frameNumber != -1) + args.insert(_(FRAMENUMBER), frameNumber); + + jsonVal.insert(_(ARGUMENTS), args); + } + + sendAndLogV8Request(jsonVal); +} + +void QmlEnginePrivate::scripts(int types, const QList ids, bool includeSource, + const QVariant filter) +{ + // { "seq" : , + // "type" : "request", + // "command" : "scripts", + // "arguments" : { "types" : + // "ids" : + // "includeSource" : + // "filter" : + // } + // } + QJsonObject jsonVal = initObject(); + jsonVal.insert(_(COMMAND), _(SCRIPTS)); + + QJsonObject args { + { _(TYPES), types } + }; + + if (ids.count()) { + QJsonArray array; + foreach (int id, ids) { + array.push_back(id); + } + args.insert(_(IDS), array); + } + + if (includeSource) + args.insert(_(INCLUDESOURCE), includeSource); + + QJsonValue filterValue; + if (filter.type() == QVariant::String) + filterValue = filter.toString(); + else if (filter.type() == QVariant::Int) + filterValue = filter.toInt(); + else + QTC_CHECK(!filter.isValid()); + + args.insert(_(FILTER), filterValue); + + jsonVal.insert(_(ARGUMENTS), args); + + sendAndLogV8Request(jsonVal); +} + +void QmlEnginePrivate::setBreakpoint(const QString type, const QString target, + bool enabled, int line, int column, + const QString condition, int ignoreCount) +{ + // { "seq" : , + // "type" : "request", + // "command" : "setbreakpoint", + // "arguments" : { "type" : <"function" or "script" or "scriptId" or "scriptRegExp"> + // "target" : + // "line" : + // "column" : + // "enabled" : + // "condition" : + // "ignoreCount" : + // } + // } + if (type == _(EVENT)) { + QByteArray params; + QmlDebugStream rs(¶ms, QIODevice::WriteOnly); + rs << target.toUtf8() << enabled; + engine->showMessage(QString(_("%1 %2 %3")).arg(_(BREAKONSIGNAL), target, enabled ? _("enabled") : _("disabled")), LogInput); + sendMessage(packMessage(BREAKONSIGNAL, params)); + + } else { + QJsonObject jsonVal = initObject(); + jsonVal.insert(_(COMMAND), _(SETBREAKPOINT)); + + QJsonObject args { + { _(TYPE), type }, + { _(ENABLED), enabled } + }; + if (type == _(SCRIPTREGEXP)) + args.insert(_(TARGET), Utils::FileName::fromString(target).fileName()); + else + args.insert(_(TARGET), target); + + if (line) + args.insert(_(LINE), line - 1); + + if (column) + args.insert(_(COLUMN), column - 1); + + if (!condition.isEmpty()) + args.insert(_(CONDITION), condition); + + if (ignoreCount != -1) + args.insert(_(IGNORECOUNT), ignoreCount); + + jsonVal.insert(_(ARGUMENTS), args); + + sendAndLogV8Request(jsonVal); + } +} + +void QmlEnginePrivate::clearBreakpoint(int breakpoint) +{ + // { "seq" : , + // "type" : "request", + // "command" : "clearbreakpoint", + // "arguments" : { "breakpoint" : + // } + // } + QJsonObject jsonVal = initObject(); + jsonVal.insert(_(COMMAND), _(CLEARBREAKPOINT)); + + QJsonObject args { + { _(BREAKPOINT), breakpoint } + }; + + jsonVal.insert(_(ARGUMENTS), args); + + sendAndLogV8Request(jsonVal); +} + +void QmlEnginePrivate::setExceptionBreak(Exceptions type, bool enabled) +{ + // { "seq" : , + // "type" : "request", + // "command" : "setexceptionbreak", + // "arguments" : { "type" : , + // "enabled" : + // } + // } + QJsonObject jsonVal = initObject(); + jsonVal.insert(_(COMMAND), _(SETEXCEPTIONBREAK)); + + QJsonObject args; + + if (type == AllExceptions) + args.insert(_(TYPE), _(ALL)); + //Not Supported + // else if (type == UncaughtExceptions) + // args.setProperty(_(TYPE),QScriptValue(_(UNCAUGHT))); + + if (enabled) + args.insert(_(ENABLED), enabled); + + jsonVal.insert(_(ARGUMENTS), args); + + sendAndLogV8Request(jsonVal); +} + +void QmlEnginePrivate::version() +{ + // { "seq" : , + // "type" : "request", + // "command" : "version", + // } + QJsonObject jsonVal = initObject(); + jsonVal.insert(_(COMMAND), _(VERSION)); + + sendAndLogV8Request(jsonVal); +} + +QVariant valueFromRef(int handle, const QVariant &refsVal, bool *success) +{ + *success = false; + QVariant variant; + const QVariantList refs = refsVal.toList(); + foreach (const QVariant &ref, refs) { + const QVariantMap refData = ref.toMap(); + if (refData.value(_(HANDLE)).toInt() == handle) { + variant = refData; + *success = true; + break; } } - return success; + return variant; +} + +QmlV8ObjectData extractData(const QVariant &data, const QVariant &refsVal) +{ + // { "handle" : , + // "type" : <"undefined", "null", "boolean", "number", "string", "object", "function" or "frame"> + // } + + // {"handle":,"type":"undefined"} + + // {"handle":,"type":"null"} + + // { "handle":, + // "type" : <"boolean", "number" or "string"> + // "value" : + // } + + // {"handle":7,"type":"boolean","value":true} + + // {"handle":8,"type":"number","value":42} + + // { "handle" : , + // "type" : "object", + // "className" : , + // "constructorFunction" : {"ref":}, + // "protoObject" : {"ref":}, + // "prototypeObject" : {"ref":}, + // "properties" : [ {"name" : , + // "ref" : + // }, + // ... + // ] + // } + + // { "handle" : , + // "type" : "function", + // "className" : "Function", + // "constructorFunction" : {"ref":}, + // "protoObject" : {"ref":}, + // "prototypeObject" : {"ref":}, + // "name" : , + // "inferredName" : + // "source" : , + // "script" : , + // "scriptId" : , + // "position" : , + // "line" : , + // "column" : , + // "properties" : [ {"name" : , + // "ref" : + // }, + // ... + // ] + // } + + QmlV8ObjectData objectData; + const QVariantMap dataMap = data.toMap(); + + objectData.name = dataMap.value(_(NAME)).toByteArray(); + + if (dataMap.contains(_(REF))) { + objectData.handle = dataMap.value(_(REF)).toInt(); + bool success; + QVariant dataFromRef = valueFromRef(objectData.handle, refsVal, &success); + if (success) { + QmlV8ObjectData data = extractData(dataFromRef, refsVal); + objectData.type = data.type; + objectData.value = data.value; + objectData.properties = data.properties; + } + } else { + objectData.handle = dataMap.value(_(HANDLE)).toInt(); + QString type = dataMap.value(_(TYPE)).toString(); + + if (type == _("undefined")) { + objectData.type = QByteArray("undefined"); + objectData.value = QVariant(_("undefined")); + + } else if (type == _("null")) { + objectData.type = QByteArray("null"); + objectData.value= QVariant(_("null")); + + } else if (type == _("boolean")) { + objectData.type = QByteArray("boolean"); + objectData.value = dataMap.value(_(VALUE)); + + } else if (type == _("number")) { + objectData.type = QByteArray("number"); + objectData.value = dataMap.value(_(VALUE)); + + } else if (type == _("string")) { + objectData.type = QByteArray("string"); + objectData.value = dataMap.value(_(VALUE)); + + } else if (type == _("object")) { + objectData.type = QByteArray("object"); + objectData.value = dataMap.value(_("className")); + objectData.properties = dataMap.value(_("properties")).toList(); + + } else if (type == _("function")) { + objectData.type = QByteArray("function"); + objectData.value = dataMap.value(_(NAME)); + objectData.properties = dataMap.value(_("properties")).toList(); + + } else if (type == _("script")) { + objectData.type = QByteArray("script"); + objectData.value = dataMap.value(_(NAME)); + } + } + + return objectData; +} + +void QmlEnginePrivate::clearCache() +{ + currentFrameScopes.clear(); + updateLocalsAndWatchers.clear(); +} + +QByteArray QmlEnginePrivate::packMessage(const QByteArray &type, const QByteArray &message) +{ + SDEBUG(message); + QByteArray request; + QmlDebugStream rs(&request, QIODevice::WriteOnly); + QByteArray cmd = V8DEBUG; + rs << cmd << type << message; + return request; +} + +QJsonObject QmlEnginePrivate::initObject() +{ + return QJsonObject { + {_(SEQ), ++sequence}, + {_(TYPE), _(REQUEST)} + }; +} + +void QmlEnginePrivate::sendAndLogV8Request(const QJsonObject &request) +{ + const QByteArray msg = QJsonDocument(request).toJson(QJsonDocument::Compact); + engine->showMessage(QString::fromLatin1("%1 %2").arg(_(V8REQUEST), QString::fromUtf8(msg)), LogInput); + sendMessage(packMessage(V8REQUEST, msg)); +} + +void QmlEnginePrivate::expandObject(const QByteArray &iname, quint64 objectId) +{ + if (objectId == 0) { + //We may have got the global object + const WatchItem *watch = engine->watchHandler()->findItem(iname); + if (watch->value == QLatin1String("global")) { + StackHandler *stackHandler = engine->stackHandler(); + if (stackHandler->isContentsValid() && stackHandler->currentFrame().isUsable()) { + evaluate(watch->name, false, false, stackHandler->currentIndex()); + evaluatingExpression.insert(sequence, QLatin1String(iname)); + } + return; + } + } + localsAndWatchers.insertMulti(objectId, iname); + lookup(QList() << objectId); +} + +void QmlEnginePrivate::messageReceived(const QByteArray &data) +{ + QmlDebugStream ds(data); + QByteArray command; + ds >> command; + + if (command == V8DEBUG) { + QByteArray type; + QByteArray response; + ds >> type >> response; + + engine->showMessage(QLatin1String(type), LogOutput); + if (type == CONNECT) { + //debugging session started + + } else if (type == INTERRUPT) { + //debug break requested + + } else if (type == BREAKONSIGNAL) { + //break on signal handler requested + + } else if (type == V8MESSAGE) { + const QString responseString = QLatin1String(response); + SDEBUG(responseString); + engine->showMessage(QLatin1String(V8MESSAGE) + QLatin1Char(' ') + responseString, LogOutput); + + const QVariantMap resp = + QJsonDocument::fromJson(responseString.toUtf8()).toVariant().toMap(); + + const QString type(resp.value(_(TYPE)).toString()); + + if (type == _("response")) { + + bool success = resp.value(_("success")).toBool(); + if (!success) { + SDEBUG("Request was unsuccessful"); + } + + const QString debugCommand(resp.value(_(COMMAND)).toString()); + + if (debugCommand == _(DISCONNECT)) { + //debugging session ended + + } else if (debugCommand == _(CONTINEDEBUGGING)) { + //do nothing, wait for next break + + } else if (debugCommand == _(BACKTRACE)) { + if (success) + updateStack(resp.value(_(BODY)), resp.value(_(REFS))); + + } else if (debugCommand == _(LOOKUP)) { + if (success) + expandLocalsAndWatchers(resp.value(_(BODY)), resp.value(_(REFS))); + + } else if (debugCommand == _(EVALUATE)) { + int seq = resp.value(_("request_seq")).toInt(); + if (success) { + updateEvaluationResult(seq, success, resp.value(_(BODY)), resp.value(_(REFS))); + } else { + QVariantMap map; + map.insert(_(TYPE), QVariant(_("string"))); + map.insert(_(VALUE), resp.value(_("message"))); + updateEvaluationResult(seq, success, QVariant(map), QVariant()); + } + + } else if (debugCommand == _(SETBREAKPOINT)) { + // { "seq" : , + // "type" : "response", + // "request_seq" : , + // "command" : "setbreakpoint", + // "body" : { "type" : <"function" or "script"> + // "breakpoint" : + // } + // "running" : + // "success" : true + // } + + int seq = resp.value(_("request_seq")).toInt(); + const QVariantMap breakpointData = resp.value(_(BODY)).toMap(); + int index = breakpointData.value(_("breakpoint")).toInt(); + + if (breakpointsSync.contains(seq)) { + BreakpointModelId id = breakpointsSync.take(seq); + breakpoints.insert(id, index); + + //Is actual position info present? Then breakpoint was + //accepted + const QVariantList actualLocations = + breakpointData.value(_("actual_locations")).toList(); + if (actualLocations.count()) { + //The breakpoint requested line should be same as + //actual line + BreakHandler *handler = engine->breakHandler(); + Breakpoint bp = handler->breakpointById(id); + if (bp.state() != BreakpointInserted) { + BreakpointResponse br = bp.response(); + br.lineNumber = breakpointData.value(_("line")).toInt() + 1; + bp.setResponse(br); + bp.notifyBreakpointInsertOk(); + } + } + + + } else { + breakpointsTemp.append(index); + } + + + } else if (debugCommand == _(CLEARBREAKPOINT)) { + // DO NOTHING + + } else if (debugCommand == _(SETEXCEPTIONBREAK)) { + // { "seq" : , + // "type" : "response", + // "request_seq" : , + // "command" : "setexceptionbreak", + // "body" : { "type" : , + // "enabled" : + // } + // "running" : true + // "success" : true + // } + + + } else if (debugCommand == _(FRAME)) { + if (success) + setCurrentFrameDetails(resp.value(_(BODY)), resp.value(_(REFS))); + + } else if (debugCommand == _(SCOPE)) { + if (success) + updateScope(resp.value(_(BODY)), resp.value(_(REFS))); + + } else if (debugCommand == _(SCRIPTS)) { + // { "seq" : , + // "type" : "response", + // "request_seq" : , + // "command" : "scripts", + // "body" : [ { "name" : , + // "id" : + // "lineOffset" : + // "columnOffset" : + // "lineCount" : + // "data" : + // "source" : + // "sourceStart" : + // "sourceLength" : + // "scriptType" :