qmlengine.cpp 90.8 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
Eike Ziller's avatar
Eike Ziller committed
3 4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8 9 10 11
** 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
Eike Ziller's avatar
Eike Ziller committed
12 13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** use the contact form at http://www.qt.io/contact-us.
15 16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18 19 20 21 22 23
** 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.
hjk's avatar
hjk committed
24
**
Eike Ziller's avatar
Eike Ziller committed
25 26
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
con's avatar
con committed
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
30 31

#include "qmlengine.h"
hjk's avatar
hjk committed
32 33 34

#include "interactiveinterpreter.h"
#include "qmlinspectoradapter.h"
35
#include "qmlinspectoragent.h"
hjk's avatar
hjk committed
36 37
#include "qmlv8debuggerclientconstants.h"
#include "qmlengineutils.h"
38

hjk's avatar
hjk committed
39
#include <debugger/breakhandler.h>
40 41 42
#include <debugger/debuggeractions.h>
#include <debugger/debuggercore.h>
#include <debugger/debuggerinternalconstants.h>
43
#include <debugger/debuggerruncontrol.h>
44 45
#include <debugger/debuggerstringutils.h>
#include <debugger/debuggertooltipmanager.h>
hjk's avatar
hjk committed
46 47
#include <debugger/sourcefileshandler.h>
#include <debugger/stackhandler.h>
Christian Kandeler's avatar
Christian Kandeler committed
48
#include <debugger/threaddata.h>
hjk's avatar
hjk committed
49
#include <debugger/watchhandler.h>
50
#include <debugger/watchwindow.h>
51

hjk's avatar
hjk committed
52 53 54 55 56
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/helpmanager.h>
#include <coreplugin/icore.h>

#include <projectexplorer/applicationlauncher.h>
57

58
#include <qmljseditor/qmljseditorconstants.h>
59
#include <qmljs/qmljsmodelmanagerinterface.h>
60
#include <qmljs/consolemanagerinterface.h>
61

62 63
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
hjk's avatar
hjk committed
64

65
#include <utils/treemodel.h>
hjk's avatar
hjk committed
66
#include <utils/qtcassert.h>
67

68 69
#include <QDebug>
#include <QDir>
70
#include <QDockWidget>
hjk's avatar
hjk committed
71 72 73 74
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
75 76
#include <QMessageBox>
#include <QPlainTextEdit>
hjk's avatar
hjk committed
77
#include <QTimer>
78

hjk's avatar
hjk committed
79
#define DEBUG_QML 0
80 81 82 83 84 85 86
#if DEBUG_QML
#   define SDEBUG(s) qDebug() << s
#else
#   define SDEBUG(s)
#endif
# define XSDEBUG(s) qDebug() << s

87 88
#define CB(callback) [this](const QVariantMap &r) { callback(r); }

hjk's avatar
hjk committed
89 90 91
using namespace Core;
using namespace ProjectExplorer;
using namespace QmlDebug;
92
using namespace QmlJS;
hjk's avatar
hjk committed
93
using namespace TextEditor;
94
using namespace Utils;
hjk's avatar
hjk committed
95

96 97 98
namespace Debugger {
namespace Internal {

hjk's avatar
hjk committed
99
enum Exceptions
100
{
hjk's avatar
hjk committed
101 102 103 104
    NoExceptions,
    UncaughtExceptions,
    AllExceptions
};
105

hjk's avatar
hjk committed
106
enum StepAction
107
{
hjk's avatar
hjk committed
108 109 110 111 112
    Continue,
    StepIn,
    StepOut,
    Next
};
113

hjk's avatar
hjk committed
114 115 116 117 118 119 120 121
struct QmlV8ObjectData
{
    int handle;
    QByteArray name;
    QByteArray type;
    QVariant value;
    QVariantList properties;
};
122

123 124
typedef std::function<void(const QVariantMap &)> QmlCallback;

125 126 127 128 129 130 131 132
struct LookupData
{
    QByteArray iname;
    QString name;
};

typedef QMultiHash<int, LookupData> LookupItems; // id -> (iname, exp)

hjk's avatar
hjk committed
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
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 continueDebugging(StepAction stepAction);

149 150
    void evaluate(const QString expr, const QmlCallback &cb);
    void lookup(const LookupItems &items);
151
    void backtrace();
152
    void updateLocals();
153
    void scope(int number, int frameNumber = -1);
hjk's avatar
hjk committed
154 155 156 157 158 159 160 161 162 163 164
    void scripts(int types = 4, const QList<int> ids = QList<int>(),
                 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 flushSendBuffer();

165 166
    void handleBacktrace(const QVariantMap &response);
    void handleLookup(const QVariantMap &response);
167
    void handleExecuteDebuggerCommand(const QVariantMap &response);
168
    void handleEvaluateExpression(const QVariantMap &response, const QByteArray &iname, const QString &expr);
169 170 171
    void handleFrame(const QVariantMap &response);
    void handleScope(const QVariantMap &response);
    void handleVersion(const QVariantMap &response);
172
    StackFrame extractStackFrame(const QVariant &bodyVal);
hjk's avatar
hjk committed
173 174 175

    bool canEvaluateScript(const QString &script);
    void updateScriptSource(const QString &fileName, int lineOffset, int columnOffset, const QString &source);
176

177
    void runCommand(const DebuggerCommand &command, const QmlCallback &cb = QmlCallback());
178
    void runDirectCommand(const QByteArray &type, const QByteArray &msg = QByteArray());
179

180 181 182 183
    void clearRefs() { refVals.clear(); }
    void memorizeRefs(const QVariant &refs);
    QmlV8ObjectData extractData(const QVariant &data) const;
    void insertSubItems(WatchItem *parent, const QVariantList &properties);
184
    void checkForFinishedUpdate();
185 186
    ConsoleItem *constructLogItemTree(ConsoleItem *parent, const QmlV8ObjectData &objectData);

hjk's avatar
hjk committed
187
public:
188
    QHash<int, QmlV8ObjectData> refVals; // The mapping of target object handles to retrieved values.
hjk's avatar
hjk committed
189 190 191 192 193 194
    int sequence = -1;
    QmlEngine *engine;
    QHash<BreakpointModelId, int> breakpoints;
    QHash<int, BreakpointModelId> breakpointsSync;
    QList<int> breakpointsTemp;

195
    LookupItems currentlyLookingUp; // Id -> inames
hjk's avatar
hjk committed
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220

    //Cache
    QList<int> currentFrameScopes;
    QHash<int, int> stackIndexLookup;

    StepAction previousStepAction = Continue;

    QList<QByteArray> sendBuffer;

    QHash<QString, QTextDocument*> sourceDocuments;
    QHash<QString, QWeakPointer<BaseTextEditor> > sourceEditors;
    InteractiveInterpreter interpreter;
    ApplicationLauncher applicationLauncher;
    QmlInspectorAdapter inspectorAdapter;
    QmlOutputParser outputParser;

    QTimer noDebugOutputTimer;
    QHash<QString,Breakpoint> pendingBreakpoints;
    QList<quint32> queryIds;
    bool retryOnConnectFail = false;
    bool automaticConnect = false;

    QTimer connectionTimer;
    QmlDebug::QmlDebugConnection *connection;
    QmlDebug::QDebugMessageClient *msgClient = 0;
221 222

    QHash<int, QmlCallback> callbackForToken;
223
};
224

hjk's avatar
hjk committed
225
static void updateDocument(IDocument *document, const QTextDocument *textDocument)
226
{
hjk's avatar
hjk committed
227 228
    if (auto baseTextDocument = qobject_cast<TextDocument *>(document))
        baseTextDocument->document()->setPlainText(textDocument->toPlainText());
229 230
}

hjk's avatar
hjk committed
231

232 233 234 235 236 237
///////////////////////////////////////////////////////////////////////
//
// QmlEngine
//
///////////////////////////////////////////////////////////////////////

238
QmlEngine::QmlEngine(const DebuggerRunParameters &startParameters, DebuggerEngine *masterEngine)
hjk's avatar
hjk committed
239 240
  : DebuggerEngine(startParameters),
    d(new QmlEnginePrivate(this, new QmlDebugConnection(this)))
241
{
Friedemann Kleint's avatar
Friedemann Kleint committed
242
    setObjectName(QLatin1String("QmlEngine"));
243

Aurindam Jana's avatar
Aurindam Jana committed
244 245 246
    if (masterEngine)
        setMasterEngine(masterEngine);

hjk's avatar
hjk committed
247 248 249 250 251
    connect(stackHandler(), &StackHandler::stackChanged,
            this, &QmlEngine::updateCurrentContext);
    connect(stackHandler(), &StackHandler::currentIndexChanged,
            this, &QmlEngine::updateCurrentContext);
    connect(inspectorView(), SIGNAL(currentIndexChanged(QModelIndex)),
252
            SLOT(updateCurrentContext()));
hjk's avatar
hjk committed
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
    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<void(QTimer::*)()>(&QTimer::start));

    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);
270

Kai Koehne's avatar
Kai Koehne committed
271 272
    // Only wait 8 seconds for the 'Waiting for connection' on application output,
    // then just try to connect (application output might be redirected / blocked)
hjk's avatar
hjk committed
273 274 275
    d->noDebugOutputTimer.setSingleShot(true);
    d->noDebugOutputTimer.setInterval(8000);
    connect(&d->noDebugOutputTimer, SIGNAL(timeout()), this, SLOT(tryToConnect()));
276

hjk's avatar
hjk committed
277 278 279
    if (auto mmIface = ModelManagerInterface::instance()) {
        connect(mmIface, &ModelManagerInterface::documentUpdated,
                this, &QmlEngine::documentUpdated);
280
    }
281
    // we won't get any debug output
282
    if (startParameters.useTerminal) {
hjk's avatar
hjk committed
283 284 285
        d->noDebugOutputTimer.setInterval(0);
        d->retryOnConnectFail = true;
        d->automaticConnect = true;
286
    }
hjk's avatar
hjk committed
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314

    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);
315 316 317
}

QmlEngine::~QmlEngine()
hjk's avatar
hjk committed
318
{
hjk's avatar
hjk committed
319
    QSet<IDocument *> documentsToClose;
320

hjk's avatar
hjk committed
321 322 323
    QHash<QString, QWeakPointer<BaseTextEditor> >::iterator iter;
    for (iter = d->sourceEditors.begin(); iter != d->sourceEditors.end(); ++iter) {
        QWeakPointer<BaseTextEditor> textEditPtr = iter.value();
324
        if (textEditPtr)
325
            documentsToClose << textEditPtr.data()->document();
326
    }
hjk's avatar
hjk committed
327 328 329
    EditorManager::closeDocuments(documentsToClose.toList());

    delete d;
hjk's avatar
hjk committed
330
}
331

hjk's avatar
hjk committed
332
void QmlEngine::setupInferior()
333
{
hjk's avatar
hjk committed
334
    QTC_ASSERT(state() == InferiorSetupRequested, qDebug() << state());
Lasse Holmstedt's avatar
Lasse Holmstedt committed
335

336
    notifyInferiorSetupOk();
337

hjk's avatar
hjk committed
338
    if (d->automaticConnect)
339
        beginConnection();
hjk's avatar
hjk committed
340
}
hjk's avatar
hjk committed
341

con's avatar
con committed
342
void QmlEngine::appendMessage(const QString &msg, Utils::OutputFormat /* format */)
hjk's avatar
hjk committed
343
{
344
    showMessage(msg, AppOutput); // FIXME: Redirect to RunControl
hjk's avatar
hjk committed
345 346
}

Lasse Holmstedt's avatar
Lasse Holmstedt committed
347 348 349 350
void QmlEngine::connectionEstablished()
{
    attemptBreakpointSynchronization();

351 352
    if (state() == EngineRunRequested)
        notifyEngineRunAndInferiorRunOk();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
353
}
354

355 356
void QmlEngine::tryToConnect(quint16 port)
{
357
    showMessage(QLatin1String("QML Debugger: No application output received in time, trying to connect ..."), LogStatus);
hjk's avatar
hjk committed
358
    d->retryOnConnectFail = true;
359 360 361
    if (state() == EngineRunRequested) {
        if (isSlaveEngine()) {
            // Probably cpp is being debugged and hence we did not get the output yet.
362
            if (!masterEngine()->isDying()) {
hjk's avatar
hjk committed
363 364
                d->noDebugOutputTimer.setInterval(4000);
                d->noDebugOutputTimer.start();
365
            }
366 367 368 369 370 371
            else
                appStartupFailed(tr("No application output received in time"));
        } else {
            beginConnection(port);
        }
    } else {
hjk's avatar
hjk committed
372
        d->automaticConnect = true;
373
    }
374 375
}

376
void QmlEngine::beginConnection(quint16 port)
377
{
hjk's avatar
hjk committed
378
    d->noDebugOutputTimer.stop();
379

hjk's avatar
hjk committed
380
    if (state() != EngineRunRequested && d->retryOnConnectFail)
381 382
        return;

383
    QTC_ASSERT(state() == EngineRunRequested, return);
384

385
    QString host =  runParameters().qmlServerAddress;
386 387 388 389
    // Use localhost as default
    if (host.isEmpty())
        host = QLatin1String("localhost");

390 391 392 393 394 395 396 397 398 399 400
    /*
     * Let plugin-specific code override the port printed by the application. This is necessary
     * in the case of port forwarding, when the port the application listens on is not the same that
     * we want to connect to.
     * NOTE: It is still necessary to wait for the output in that case, because otherwise we cannot
     * be sure that the port is already open. The usual method of trying to connect repeatedly
     * will not work, because the intermediate port is already open. So the connection
     * will be accepted on that port but the forwarding to the target port will fail and
     * the connection will be closed again (instead of returning the "connection refused"
     * error that we expect).
     */
401 402
    if (runParameters().qmlServerPort > 0)
        port = runParameters().qmlServerPort;
403

hjk's avatar
hjk committed
404 405
    if (!d->connection || d->connection->isOpen())
        return;
Lasse Holmstedt's avatar
Lasse Holmstedt committed
406

hjk's avatar
hjk committed
407 408 409 410 411
    d->connection->connectToHost(host, port);

    //A timeout to check the connection state
    d->connectionTimer.start();
}
412

413
void QmlEngine::connectionStartupFailed()
Lasse Holmstedt's avatar
Lasse Holmstedt committed
414
{
hjk's avatar
hjk committed
415
    if (d->retryOnConnectFail) {
416 417
        // retry after 3 seconds ...
        QTimer::singleShot(3000, this, SLOT(beginConnection()));
418 419
        return;
    }
420

hjk's avatar
hjk committed
421
    QMessageBox *infoBox = new QMessageBox(ICore::mainWindow());
422 423
    infoBox->setIcon(QMessageBox::Critical);
    infoBox->setWindowTitle(tr("Qt Creator"));
424 425 426 427 428
    infoBox->setText(tr("Could not connect to the in-process QML debugger."
                        "\nDo you want to retry?"));
    infoBox->setStandardButtons(QMessageBox::Retry | QMessageBox::Cancel |
                                QMessageBox::Help);
    infoBox->setDefaultButton(QMessageBox::Retry);
429 430
    infoBox->setModal(true);

hjk's avatar
hjk committed
431 432
    connect(infoBox, &QDialog::finished,
            this, &QmlEngine::errorMessageBoxFinished);
433 434 435 436

    infoBox->show();
}

437 438
void QmlEngine::appStartupFailed(const QString &errorMessage)
{
439 440 441 442
    QString error = tr("Could not connect to the in-process QML debugger."
                       "\n%1").arg(errorMessage);

    if (isMasterEngine()) {
hjk's avatar
hjk committed
443
        QMessageBox *infoBox = new QMessageBox(ICore::mainWindow());
444 445 446 447 448
        infoBox->setIcon(QMessageBox::Critical);
        infoBox->setWindowTitle(tr("Qt Creator"));
        infoBox->setText(error);
        infoBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Help);
        infoBox->setDefaultButton(QMessageBox::Ok);
hjk's avatar
hjk committed
449 450
        connect(infoBox, &QDialog::finished,
                this, &QmlEngine::errorMessageBoxFinished);
451 452 453 454
        infoBox->show();
    } else {
        showMessage(error, StatusBar);
    }
455 456 457 458

    notifyEngineRunFailed();
}

459
void QmlEngine::errorMessageBoxFinished(int result)
460 461
{
    switch (result) {
462
    case QMessageBox::Retry: {
463
        beginConnection();
464 465 466
        break;
    }
    case QMessageBox::Help: {
hjk's avatar
hjk committed
467
        HelpManager::handleHelpRequest(QLatin1String("qthelp://org.qt-project.qtcreator/doc/creator-debugging-qml.html"));
468
        // fall through
469 470
    }
    default:
471 472 473
        if (state() == InferiorRunOk) {
            notifyInferiorSpontaneousStop();
            notifyInferiorIll();
474
        } else if (state() == EngineRunRequested) {
475
            notifyEngineRunFailed();
476
        }
Kai Koehne's avatar
Kai Koehne committed
477
        break;
478
    }
Lasse Holmstedt's avatar
Lasse Holmstedt committed
479 480
}

hjk's avatar
hjk committed
481
void QmlEngine::filterApplicationMessage(const QString &output, int /*channel*/) const
Lasse Holmstedt's avatar
Lasse Holmstedt committed
482
{
hjk's avatar
hjk committed
483
    d->outputParser.processOutput(output);
484 485 486 487
}

void QmlEngine::showMessage(const QString &msg, int channel, int timeout) const
{
488
    if (channel == AppOutput || channel == AppError)
hjk's avatar
hjk committed
489
        filterApplicationMessage(msg, channel);
490 491 492
    DebuggerEngine::showMessage(msg, channel, timeout);
}

493 494 495
void QmlEngine::gotoLocation(const Location &location)
{
    const QString fileName = location.fileName();
496
    if (QUrl(fileName).isLocalFile()) {
497
        // internal file from source files -> show generated .js
hjk's avatar
hjk committed
498
        QTC_ASSERT(d->sourceDocuments.contains(fileName), return);
499

Aurindam Jana's avatar
Aurindam Jana committed
500
        QString titlePattern = tr("JS Source for %1").arg(fileName);
501
        //Check if there are open documents with the same title
hjk's avatar
hjk committed
502
        foreach (IDocument *document, DocumentModel::openedDocuments()) {
503
            if (document->displayName() == titlePattern) {
hjk's avatar
hjk committed
504
                EditorManager::activateEditorForDocument(document);
505
                return;
Aurindam Jana's avatar
Aurindam Jana committed
506 507
            }
        }
hjk's avatar
hjk committed
508
        IEditor *editor = EditorManager::openEditorWithContents(
509 510 511
                    QmlJSEditor::Constants::C_QMLJSEDITOR_ID, &titlePattern);
        if (editor) {
            editor->document()->setProperty(Constants::OPENED_BY_DEBUGGER, true);
hjk's avatar
hjk committed
512
            if (auto plainTextEdit = qobject_cast<QPlainTextEdit *>(editor->widget()))
513
                plainTextEdit->setReadOnly(true);
hjk's avatar
hjk committed
514
            updateDocument(editor->document(), d->sourceDocuments.value(fileName));
515
        }
516 517 518 519 520
    } else {
        DebuggerEngine::gotoLocation(location);
    }
}

521 522
void QmlEngine::closeConnection()
{
hjk's avatar
hjk committed
523 524 525 526 527 528
    if (d->connectionTimer.isActive()) {
        d->connectionTimer.stop();
    } else {
        if (d->connection)
            d->connection->close();
    }
529 530
}

hjk's avatar
hjk committed
531 532 533
void QmlEngine::runEngine()
{
    QTC_ASSERT(state() == EngineRunRequested, qDebug() << state());
Lasse Holmstedt's avatar
Lasse Holmstedt committed
534

535
    if (!isSlaveEngine()) {
536
        if (runParameters().startMode == AttachToRemoteServer)
hjk's avatar
hjk committed
537
            d->noDebugOutputTimer.start();
538
        else if (runParameters().startMode == AttachToRemoteProcess)
539 540
            beginConnection();
        else
541
            startApplicationLauncher();
542
    } else {
hjk's avatar
hjk committed
543
        d->noDebugOutputTimer.start();
544
    }
545 546 547 548
}

void QmlEngine::startApplicationLauncher()
{
hjk's avatar
hjk committed
549
    if (!d->applicationLauncher.isRunning()) {
550
        appendMessage(tr("Starting %1 %2").arg(
551 552
                          QDir::toNativeSeparators(runParameters().executable),
                          runParameters().processArgs)
553
                      + QLatin1Char('\n')
con's avatar
con committed
554
                     , Utils::NormalMessageFormat);
hjk's avatar
hjk committed
555
        d->applicationLauncher.start(ApplicationLauncher::Gui,
556 557
                                    runParameters().executable,
                                    runParameters().processArgs);
558
    }
559
}
Lasse Holmstedt's avatar
Lasse Holmstedt committed
560

561 562
void QmlEngine::stopApplicationLauncher()
{
hjk's avatar
hjk committed
563 564 565 566
    if (d->applicationLauncher.isRunning()) {
        disconnect(&d->applicationLauncher, &ApplicationLauncher::processExited,
                   this, &QmlEngine::disconnected);
        d->applicationLauncher.stop();
567
    }
568 569
}

570
void QmlEngine::notifyEngineRemoteSetupFinished(const RemoteSetupResult &result)
571
{
572
    DebuggerEngine::notifyEngineRemoteSetupFinished(result);
573

574 575
    if (result.success) {
        if (result.qmlServerPort != InvalidPort)
576
            runParameters().qmlServerPort = result.qmlServerPort;
577

578
        notifyEngineSetupOk();
579

580 581 582
        // The remote setup can take while especialy with mixed debugging.
        // Just waiting for 8 seconds is not enough. Increase the timeout
        // to 60 s
hjk's avatar
hjk committed
583 584
        // In case we get an output the d->outputParser will start the connection.
        d->noDebugOutputTimer.setInterval(60000);
585 586 587
    }
    else {
        if (isMasterEngine())
hjk's avatar
hjk committed
588
            QMessageBox::critical(ICore::dialogParent(), tr("Failed to start application"),
589 590 591
                                  tr("Application startup failed: %1").arg(result.reason));
        notifyEngineSetupFailed();
    }
592 593
}

Aurindam Jana's avatar
Aurindam Jana committed
594 595 596 597 598
void QmlEngine::notifyEngineRemoteServerRunning(const QByteArray &serverChannel, int pid)
{
    bool ok = false;
    quint16 qmlPort = serverChannel.toUInt(&ok);
    if (ok)
599
        runParameters().qmlServerPort = qmlPort;
Aurindam Jana's avatar
Aurindam Jana committed
600
    else
Friedemann Kleint's avatar
Friedemann Kleint committed
601
        qWarning() << tr("QML debugging port not set: Unable to convert %1 to unsigned int.").arg(QString::fromLatin1(serverChannel));
Aurindam Jana's avatar
Aurindam Jana committed
602 603 604 605 606

    DebuggerEngine::notifyEngineRemoteServerRunning(serverChannel, pid);
    notifyEngineSetupOk();

    // The remote setup can take a while especially with mixed debugging.
hjk's avatar
hjk committed
607 608 609
    // 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);
Aurindam Jana's avatar
Aurindam Jana committed
610 611
}

hjk's avatar
hjk committed
612
void QmlEngine::shutdownInferior()
613
{
hjk's avatar
hjk committed
614
    // End session.
615 616 617 618 619
    //    { "seq"     : <number>,
    //      "type"    : "request",
    //      "command" : "disconnect",
    //    }
    d->runCommand(DISCONNECT);
620

621
    if (isSlaveEngine())
622 623
        resetLocation();
    stopApplicationLauncher();
624
    closeConnection();
625

hjk's avatar
hjk committed
626 627 628 629 630
    notifyInferiorShutdownOk();
}

void QmlEngine::shutdownEngine()
{
hjk's avatar
hjk committed
631
    clearExceptionSelection();
632

hjk's avatar
hjk committed
633 634 635
    if (auto consoleManager = ConsoleManagerInterface::instance())
        consoleManager->setScriptEvaluator(0);
    d->noDebugOutputTimer.stop();
636 637

   // double check (ill engine?):
638
    stopApplicationLauncher();
Lasse Holmstedt's avatar
Lasse Holmstedt committed
639

640
    notifyEngineShutdownOk();
641 642
    if (!isSlaveEngine())
        showMessage(QString(), StatusBar);
643 644
}

hjk's avatar
hjk committed
645
void QmlEngine::setupEngine()
646
{
647
    if (runParameters().remoteSetupNeeded) {
648
        // we need to get the port first
649
        notifyEngineRequestRemoteSetup();
650
    } else {
hjk's avatar
hjk committed
651 652
        d->applicationLauncher.setEnvironment(runParameters().environment);
        d->applicationLauncher.setWorkingDirectory(runParameters().workingDirectory);
Lasse Holmstedt's avatar
Lasse Holmstedt committed
653

654
        // We can't do this in the constructore because runControl() isn't yet defined
hjk's avatar
hjk committed
655 656
        connect(&d->applicationLauncher, &ApplicationLauncher::bringToForegroundRequested,
                runControl(), &RunControl::bringApplicationToForeground,
657 658 659 660
                Qt::UniqueConnection);

        notifyEngineSetupOk();
    }
661 662
}

663 664
void QmlEngine::continueInferior()
{
hjk's avatar
hjk committed
665
    QTC_ASSERT(state() == InferiorStopOk, qDebug() << state());
hjk's avatar
hjk committed
666 667
    clearExceptionSelection();
    d->continueDebugging(Continue);
668
    resetLocation();
hjk's avatar
hjk committed
669 670
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
671 672 673 674
}

void QmlEngine::interruptInferior()
{
hjk's avatar
hjk committed
675
    showMessage(_(INTERRUPT), LogInput);
676
    d->runDirectCommand(INTERRUPT);
677
    notifyInferiorStopOk();
678 679 680 681
}

void QmlEngine::executeStep()
{
hjk's avatar
hjk committed
682 683
    clearExceptionSelection();
    d->continueDebugging(StepIn);
hjk's avatar
hjk committed
684 685
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
686 687 688 689
}

void QmlEngine::executeStepI()
{
hjk's avatar
hjk committed
690 691
    clearExceptionSelection();
    d->continueDebugging(StepIn);
hjk's avatar
hjk committed
692 693
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
694 695 696 697
}

void QmlEngine::executeStepOut()
{
hjk's avatar
hjk committed
698 699
    clearExceptionSelection();
    d->continueDebugging(StepOut);
hjk's avatar
hjk committed
700 701
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
702 703 704 705
}

void QmlEngine::executeNext()
{
hjk's avatar
hjk committed
706 707
    clearExceptionSelection();
    d->continueDebugging(Next);
hjk's avatar
hjk committed
708 709
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
710 711 712 713
}

void QmlEngine::executeNextI()
{
714
    executeNext();
715 716
}

717
void QmlEngine::executeRunToLine(const ContextData &data)
718
{
719
    QTC_ASSERT(state() == InferiorStopOk, qDebug() << state());
Jarek Kobus's avatar
Jarek Kobus committed
720
    showStatusMessage(tr("Run to line %1 (%2) requested...").arg(data.lineNumber).arg(data.fileName), 5000);
721
    resetLocation();
722 723 724 725 726 727
    ContextData modifiedData = data;
    quint32 line = data.lineNumber;
    quint32 column;
    bool valid;
    if (adjustBreakpointLineAndColumn(data.fileName, &line, &column, &valid))
        modifiedData.lineNumber = line;
hjk's avatar
hjk committed
728 729 730 731 732
    d->setBreakpoint(QString(_(SCRIPTREGEXP)), modifiedData.fileName,
                     true, modifiedData.lineNumber);
    clearExceptionSelection();
    d->continueDebugging(Continue);

733 734
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
735 736 737 738 739 740 741 742
}

void QmlEngine::executeRunToFunction(const QString &functionName)
{
    Q_UNUSED(functionName)
    XSDEBUG("FIXME:  QmlEngine::executeRunToFunction()");
}

743
void QmlEngine::executeJumpToLine(const ContextData &data)
744
{
745
    Q_UNUSED(data)
746 747 748 749 750
    XSDEBUG("FIXME:  QmlEngine::executeJumpToLine()");
}

void QmlEngine::activateFrame(int index)
{
751 752 753
    if (state() != InferiorStopOk && state() != InferiorUnrunnable)
        return;

hjk's avatar
hjk committed
754
    stackHandler()->setCurrentIndex(index);
755
    gotoLocation(stackHandler()->frames().value(index));
756 757

    d->updateLocals();
758 759
}

hjk's avatar
hjk committed
760
void QmlEngine::selectThread(ThreadId threadId)
761
{
hjk's avatar
hjk committed
762
    Q_UNUSED(threadId)
763 764
}

765
void QmlEngine::insertBreakpoint(Breakpoint bp)
766
{
767 768 769
    BreakpointState state = bp.state();
    QTC_ASSERT(state == BreakpointInsertRequested, qDebug() << bp << this << state);
    bp.notifyBreakpointInsertProceeding();
770

771
    const BreakpointParameters &params = bp.parameters();
772 773 774 775 776 777
    quint32 line = params.lineNumber;
    quint32 column = 0;
    if (params.type == BreakpointByFileAndLine) {
        bool valid = false;
        if (!adjustBreakpointLineAndColumn(params.fileName, &line, &column,
                                           &valid)) {
hjk's avatar
hjk committed
778
            d->pendingBreakpoints.insertMulti(params.fileName, bp);
779 780 781 782 783 784
            return;
        }
        if (!valid)
            return;
    }

hjk's avatar
hjk committed
785 786 787 788 789 790 791 792 793 794 795 796
    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();
797
    }
hjk's avatar
hjk committed
798 799

    d->breakpointsSync.insert(d->sequence, bp.id());
800 801
}

802
void QmlEngine::removeBreakpoint(Breakpoint bp)
803
{
804
    const BreakpointParameters &params = bp.parameters();
805
    if (params.type == BreakpointByFileAndLine &&
hjk's avatar
hjk committed
806 807 808
            d->pendingBreakpoints.contains(params.fileName)) {
        auto it = d->pendingBreakpoints.find(params.fileName);
        while (it != d->pendingBreakpoints.end() && it.key() == params.fileName) {
809
            if (it.value() == bp.id()) {
hjk's avatar
hjk committed
810
                d->pendingBreakpoints.erase(it);
811 812
                return;
            }
813
            ++it;
814 815 816
        }
    }

817 818 819
    BreakpointState state = bp.state();
    QTC_ASSERT(state == BreakpointRemoveRequested, qDebug() << bp << this << state);
    bp.notifyBreakpointRemoveProceeding();
820

hjk's avatar
hjk committed
821 822 823 824 825 826 827 828 829
    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);
830

831 832
    if (bp.state() == BreakpointRemoveProceeding)
        bp.notifyBreakpointRemoveOk();
833 834
}

835
void QmlEngine::changeBreakpoint(Breakpoint bp)
836
{
837 838 839
    BreakpointState state = bp.state();
    QTC_ASSERT(state == BreakpointChangeRequested, qDebug() << bp << this << state);
    bp.notifyBreakpointChangeProceeding();
840

hjk's avatar
hjk committed
841 842 843 844 845 846 847 848 849 850 851
    const BreakpointParameters &params = 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);
852
    } else {
hjk's avatar
hjk committed
853 854 855 856 857 858
        //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);
859 860
    }