qmlengine.cpp 90.1 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3 4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://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
12 13 14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
15
**
16 17 18 19 20 21 22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
con's avatar
con committed
23
**
hjk's avatar
hjk committed
24
****************************************************************************/
25 26

#include "qmlengine.h"
hjk's avatar
hjk committed
27 28

#include "interactiveinterpreter.h"
29
#include "qmlinspectoragent.h"
hjk's avatar
hjk committed
30 31
#include "qmlv8debuggerclientconstants.h"
#include "qmlengineutils.h"
32

hjk's avatar
hjk committed
33
#include <debugger/breakhandler.h>
34 35 36
#include <debugger/debuggeractions.h>
#include <debugger/debuggercore.h>
#include <debugger/debuggerinternalconstants.h>
37
#include <debugger/debuggerruncontrol.h>
38
#include <debugger/debuggertooltipmanager.h>
hjk's avatar
hjk committed
39 40
#include <debugger/sourcefileshandler.h>
#include <debugger/stackhandler.h>
41
#include <debugger/threaddata.h>
hjk's avatar
hjk committed
42
#include <debugger/watchhandler.h>
43
#include <debugger/watchwindow.h>
hjk's avatar
hjk committed
44
#include <debugger/console/console.h>
45

hjk's avatar
hjk committed
46 47 48 49 50
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/helpmanager.h>
#include <coreplugin/icore.h>

#include <projectexplorer/applicationlauncher.h>
51
#include <projectexplorer/runnables.h>
52

53
#include <qmljseditor/qmljseditorconstants.h>
54
#include <qmljs/qmljsmodelmanagerinterface.h>
55
#include <qmldebug/qmldebugconnection.h>
56
#include <qmldebug/qpacketprotocol.h>
57

58 59
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
hjk's avatar
hjk committed
60

61
#include <app/app_version.h>
62
#include <utils/treemodel.h>
hjk's avatar
hjk committed
63
#include <utils/basetreeview.h>
hjk's avatar
hjk committed
64
#include <utils/qtcassert.h>
65
#include <utils/qtcfallthrough.h>
66

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

79 80
#include <memory.h>

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

89
#define CB(callback) [this](const QVariantMap &r) { callback(r); }
90
#define CHECK_STATE(s) do { checkState(s, __FILE__, __LINE__); } while (0)
91

hjk's avatar
hjk committed
92 93 94
using namespace Core;
using namespace ProjectExplorer;
using namespace QmlDebug;
95
using namespace QmlJS;
hjk's avatar
hjk committed
96
using namespace TextEditor;
97
using namespace Utils;
hjk's avatar
hjk committed
98

99 100 101
namespace Debugger {
namespace Internal {

hjk's avatar
hjk committed
102
enum Exceptions
103
{
hjk's avatar
hjk committed
104 105 106 107
    NoExceptions,
    UncaughtExceptions,
    AllExceptions
};
108

hjk's avatar
hjk committed
109
enum StepAction
110
{
hjk's avatar
hjk committed
111 112 113 114 115
    Continue,
    StepIn,
    StepOut,
    Next
};
116

hjk's avatar
hjk committed
117 118
struct QmlV8ObjectData
{
119
    int handle = -1;
120
    int expectedProperties = -1;
121 122
    QString name;
    QString type;
hjk's avatar
hjk committed
123 124
    QVariant value;
    QVariantList properties;
125 126 127 128 129

    bool hasChildren() const
    {
        return expectedProperties > 0 || !properties.isEmpty();
    }
hjk's avatar
hjk committed
130
};
131

132 133
typedef std::function<void(const QVariantMap &)> QmlCallback;

134 135
struct LookupData
{
136
    QString iname;
137
    QString name;
138
    QString exp;
139 140
};

141
typedef QHash<int, LookupData> LookupItems; // id -> (iname, exp)
142

143
class QmlEnginePrivate : public QmlDebugClient
hjk's avatar
hjk committed
144 145
{
public:
146 147
    QmlEnginePrivate(QmlEngine *engine_, QmlDebugConnection *connection)
        : QmlDebugClient("V8Debugger", connection),
hjk's avatar
hjk committed
148
          engine(engine_),
149
          inspectorAgent(engine, connection)
hjk's avatar
hjk committed
150 151
    {}

152 153
    void messageReceived(const QByteArray &data) override;
    void stateChanged(State state) override;
hjk's avatar
hjk committed
154 155 156

    void continueDebugging(StepAction stepAction);

157
    void evaluate(const QString expr, qint64 context, const QmlCallback &cb);
158
    void lookup(const LookupItems &items);
159
    void backtrace();
160
    void updateLocals();
161
    void scope(int number, int frameNumber = -1);
hjk's avatar
hjk committed
162 163 164 165 166 167 168 169 170 171 172
    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();

173 174
    void handleBacktrace(const QVariantMap &response);
    void handleLookup(const QVariantMap &response);
175
    void handleExecuteDebuggerCommand(const QVariantMap &response);
176
    void handleEvaluateExpression(const QVariantMap &response, const QString &iname, const QString &expr);
177 178 179
    void handleFrame(const QVariantMap &response);
    void handleScope(const QVariantMap &response);
    void handleVersion(const QVariantMap &response);
180
    StackFrame extractStackFrame(const QVariant &bodyVal);
hjk's avatar
hjk committed
181 182 183

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

185
    void runCommand(const DebuggerCommand &command, const QmlCallback &cb = QmlCallback());
186
    void runDirectCommand(const QString &type, const QByteArray &msg = QByteArray());
187

188 189 190 191
    void clearRefs() { refVals.clear(); }
    void memorizeRefs(const QVariant &refs);
    QmlV8ObjectData extractData(const QVariant &data) const;
    void insertSubItems(WatchItem *parent, const QVariantList &properties);
192
    void checkForFinishedUpdate();
193
    ConsoleItem *constructLogItemTree(const QmlV8ObjectData &objectData);
194

hjk's avatar
hjk committed
195
public:
196
    QHash<int, QmlV8ObjectData> refVals; // The mapping of target object handles to retrieved values.
hjk's avatar
hjk committed
197 198 199 200 201 202
    int sequence = -1;
    QmlEngine *engine;
    QHash<BreakpointModelId, int> breakpoints;
    QHash<int, BreakpointModelId> breakpointsSync;
    QList<int> breakpointsTemp;

203
    LookupItems currentlyLookingUp; // Id -> inames
hjk's avatar
hjk committed
204 205 206 207 208 209 210 211 212 213 214 215 216

    //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;
217
    QmlInspectorAgent inspectorAgent;
hjk's avatar
hjk committed
218 219 220 221

    QList<quint32> queryIds;
    bool retryOnConnectFail = false;
    bool automaticConnect = false;
222
    bool unpausedEvaluate = false;
223
    bool contextEvaluate = false;
hjk's avatar
hjk committed
224 225 226

    QTimer connectionTimer;
    QmlDebug::QDebugMessageClient *msgClient = 0;
227 228

    QHash<int, QmlCallback> callbackForToken;
229 230
    QMetaObject::Connection startupMessageFilterConnection;

231 232 233 234
private:
    ConsoleItem *constructLogItemTree(const QmlV8ObjectData &objectData, QList<int> &seenHandles);
    void constructChildLogItems(ConsoleItem *item, const QmlV8ObjectData &objectData,
                             QList<int> &seenHandles);
235
};
236

hjk's avatar
hjk committed
237
static void updateDocument(IDocument *document, const QTextDocument *textDocument)
238
{
hjk's avatar
hjk committed
239 240
    if (auto baseTextDocument = qobject_cast<TextDocument *>(document))
        baseTextDocument->document()->setPlainText(textDocument->toPlainText());
241 242
}

hjk's avatar
hjk committed
243

244 245 246 247 248 249
///////////////////////////////////////////////////////////////////////
//
// QmlEngine
//
///////////////////////////////////////////////////////////////////////

250
QmlEngine::QmlEngine()
251
  :  d(new QmlEnginePrivate(this, new QmlDebugConnection(this)))
252
{
253
    setObjectName("QmlEngine");
254
    QmlDebugConnection *connection = d->connection();
255

hjk's avatar
hjk committed
256 257 258 259
    connect(stackHandler(), &StackHandler::stackChanged,
            this, &QmlEngine::updateCurrentContext);
    connect(stackHandler(), &StackHandler::currentIndexChanged,
            this, &QmlEngine::updateCurrentContext);
hjk's avatar
hjk committed
260 261
    connect(inspectorView(), &WatchTreeView::currentIndexChanged,
            this, &QmlEngine::updateCurrentContext);
hjk's avatar
hjk committed
262 263 264 265

    connect(&d->applicationLauncher, &ApplicationLauncher::processExited,
            this, &QmlEngine::disconnected);
    connect(&d->applicationLauncher, &ApplicationLauncher::appendMessage,
266
            this, &QmlEngine::appMessage);
hjk's avatar
hjk committed
267
    connect(&d->applicationLauncher, &ApplicationLauncher::processStarted,
268
            this, &QmlEngine::handleLauncherStarted);
hjk's avatar
hjk committed
269

270 271
    debuggerConsole()->setScriptEvaluator([this](const QString &expr) {
        executeDebuggerCommand(expr, QmlLanguage);
hjk's avatar
hjk committed
272
    });
hjk's avatar
hjk committed
273 274 275 276 277 278

    d->connectionTimer.setInterval(4000);
    d->connectionTimer.setSingleShot(true);
    connect(&d->connectionTimer, &QTimer::timeout,
            this, &QmlEngine::checkConnectionState);

279
    connect(connection, &QmlDebugConnection::logStateChange,
280
            this, &QmlEngine::showConnectionStateMessage);
281
    connect(connection, &QmlDebugConnection::logError, this,
282
            [this](const QString &error) { showMessage("QML Debugger: " + error, LogWarning); });
283

284
    connect(connection, &QmlDebugConnection::connectionFailed,
285
            this, &QmlEngine::connectionFailed);
286
    connect(connection, &QmlDebugConnection::connected,
hjk's avatar
hjk committed
287
            &d->connectionTimer, &QTimer::stop);
288
    connect(connection, &QmlDebugConnection::connected,
hjk's avatar
hjk committed
289
            this, &QmlEngine::connectionEstablished);
290
    connect(connection, &QmlDebugConnection::disconnected,
hjk's avatar
hjk committed
291 292
            this, &QmlEngine::disconnected);

293
    d->msgClient = new QDebugMessageClient(connection);
hjk's avatar
hjk committed
294
    connect(d->msgClient, &QDebugMessageClient::newState,
295 296 297 298
            this, [this](QmlDebugClient::State state) {
        logServiceStateChange(d->msgClient->name(), d->msgClient->serviceVersion(), state);
    });

hjk's avatar
hjk committed
299 300
    connect(d->msgClient, &QDebugMessageClient::message,
            this, &appendDebugOutput);
301 302 303
}

QmlEngine::~QmlEngine()
hjk's avatar
hjk committed
304
{
305
    QObject::disconnect(d->startupMessageFilterConnection);
hjk's avatar
hjk committed
306
    QSet<IDocument *> documentsToClose;
307

hjk's avatar
hjk committed
308 309 310
    QHash<QString, QWeakPointer<BaseTextEditor> >::iterator iter;
    for (iter = d->sourceEditors.begin(); iter != d->sourceEditors.end(); ++iter) {
        QWeakPointer<BaseTextEditor> textEditPtr = iter.value();
311
        if (textEditPtr)
312
            documentsToClose << textEditPtr.data()->document();
313
    }
hjk's avatar
hjk committed
314 315 316
    EditorManager::closeDocuments(documentsToClose.toList());

    delete d;
hjk's avatar
hjk committed
317
}
318

319 320 321 322 323 324
void QmlEngine::setState(DebuggerState state, bool forced)
{
    DebuggerEngine::setState(state, forced);
    updateCurrentContext();
}

325 326 327 328
void QmlEngine::handleLauncherStarted()
{
    // FIXME: The QmlEngine never calls notifyInferiorPid() triggering the
    // raising, so do it here manually for now.
329
    runTool()->runControl()->applicationProcessHandle().activate();
330
    tryToConnect();
331 332
}

333
void QmlEngine::appMessage(const QString &msg, Utils::OutputFormat /* format */)
hjk's avatar
hjk committed
334
{
335
    showMessage(msg, AppOutput); // FIXME: Redirect to RunControl
hjk's avatar
hjk committed
336 337
}

338 339 340 341
void QmlEngine::connectionEstablished()
{
    attemptBreakpointSynchronization();

342 343
    if (state() == EngineRunRequested)
        notifyEngineRunAndInferiorRunOk();
344
}
345

346
void QmlEngine::tryToConnect()
347
{
348
    showMessage("QML Debugger: Trying to connect ...", LogStatus);
hjk's avatar
hjk committed
349
    d->retryOnConnectFail = true;
350 351 352
    if (state() == EngineRunRequested) {
        if (isSlaveEngine()) {
            // Probably cpp is being debugged and hence we did not get the output yet.
353
            if (!masterEngine()->isDying())
354
                beginConnection();
355 356 357
            else
                appStartupFailed(tr("No application output received in time"));
        } else {
358
            beginConnection();
359 360
        }
    } else {
hjk's avatar
hjk committed
361
        d->automaticConnect = true;
362
    }
363 364
}

365
void QmlEngine::beginConnection()
366
{
hjk's avatar
hjk committed
367
    if (state() != EngineRunRequested && d->retryOnConnectFail)
368 369
        return;

370
    QTC_ASSERT(state() == EngineRunRequested, return);
371

372 373
    QObject::disconnect(d->startupMessageFilterConnection);

374
    QString host = runParameters().qmlServer.host();
375 376
    // Use localhost as default
    if (host.isEmpty())
377
        host = QHostAddress(QHostAddress::LocalHost).toString();
378

379
    // FIXME: Not needed?
380 381 382 383 384 385 386 387 388 389 390
    /*
     * 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).
     */
391
    int port = runParameters().qmlServer.port();
392

393 394
    QmlDebugConnection *connection = d->connection();
    if (!connection || connection->isConnected())
hjk's avatar
hjk committed
395
        return;
396

397
    connection->connectToHost(host, port);
hjk's avatar
hjk committed
398 399 400 401

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

403
void QmlEngine::connectionStartupFailed()
404
{
hjk's avatar
hjk committed
405
    if (d->retryOnConnectFail) {
406
        // retry after 3 seconds ...
hjk's avatar
hjk committed
407
        QTimer::singleShot(3000, this, [this] { beginConnection(); });
408 409
        return;
    }
410

hjk's avatar
hjk committed
411
    QMessageBox *infoBox = new QMessageBox(ICore::mainWindow());
412
    infoBox->setIcon(QMessageBox::Critical);
413
    infoBox->setWindowTitle(Core::Constants::IDE_DISPLAY_NAME);
414 415 416 417 418
    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);
419 420
    infoBox->setModal(true);

hjk's avatar
hjk committed
421 422
    connect(infoBox, &QDialog::finished,
            this, &QmlEngine::errorMessageBoxFinished);
423 424 425 426

    infoBox->show();
}

427 428
void QmlEngine::appStartupFailed(const QString &errorMessage)
{
429
    QString error = tr("Could not connect to the in-process QML debugger. %1").arg(errorMessage);
430 431

    if (isMasterEngine()) {
hjk's avatar
hjk committed
432
        QMessageBox *infoBox = new QMessageBox(ICore::mainWindow());
433
        infoBox->setIcon(QMessageBox::Critical);
434
        infoBox->setWindowTitle(Core::Constants::IDE_DISPLAY_NAME);
435 436 437
        infoBox->setText(error);
        infoBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Help);
        infoBox->setDefaultButton(QMessageBox::Ok);
hjk's avatar
hjk committed
438 439
        connect(infoBox, &QDialog::finished,
                this, &QmlEngine::errorMessageBoxFinished);
440 441
        infoBox->show();
    } else {
hjk's avatar
hjk committed
442
        debuggerConsole()->printItem(ConsoleItem::WarningType, error);
443
    }
444 445 446 447

    notifyEngineRunFailed();
}

448
void QmlEngine::errorMessageBoxFinished(int result)
449 450
{
    switch (result) {
451
    case QMessageBox::Retry: {
452
        beginConnection();
453 454 455
        break;
    }
    case QMessageBox::Help: {
456
        HelpManager::handleHelpRequest("qthelp://org.qt-project.qtcreator/doc/creator-debugging-qml.html");
457
        Q_FALLTHROUGH();
458 459
    }
    default:
460 461 462
        if (state() == InferiorRunOk) {
            notifyInferiorSpontaneousStop();
            notifyInferiorIll();
463
        } else if (state() == EngineRunRequested) {
464
            notifyEngineRunFailed();
465
        }
Kai Koehne's avatar
Kai Koehne committed
466
        break;
467
    }
468 469
}

470 471 472
void QmlEngine::gotoLocation(const Location &location)
{
    const QString fileName = location.fileName();
473
    if (QUrl(fileName).isLocalFile()) {
474
        // internal file from source files -> show generated .js
hjk's avatar
hjk committed
475
        QTC_ASSERT(d->sourceDocuments.contains(fileName), return);
476

Aurindam Jana's avatar
Aurindam Jana committed
477
        QString titlePattern = tr("JS Source for %1").arg(fileName);
478
        //Check if there are open documents with the same title
hjk's avatar
hjk committed
479
        foreach (IDocument *document, DocumentModel::openedDocuments()) {
480
            if (document->displayName() == titlePattern) {
hjk's avatar
hjk committed
481
                EditorManager::activateEditorForDocument(document);
482
                return;
Aurindam Jana's avatar
Aurindam Jana committed
483 484
            }
        }
hjk's avatar
hjk committed
485
        IEditor *editor = EditorManager::openEditorWithContents(
486 487 488
                    QmlJSEditor::Constants::C_QMLJSEDITOR_ID, &titlePattern);
        if (editor) {
            editor->document()->setProperty(Constants::OPENED_BY_DEBUGGER, true);
hjk's avatar
hjk committed
489
            if (auto plainTextEdit = qobject_cast<QPlainTextEdit *>(editor->widget()))
490
                plainTextEdit->setReadOnly(true);
hjk's avatar
hjk committed
491
            updateDocument(editor->document(), d->sourceDocuments.value(fileName));
492
        }
493 494 495 496 497
    } else {
        DebuggerEngine::gotoLocation(location);
    }
}

498 499
void QmlEngine::closeConnection()
{
hjk's avatar
hjk committed
500 501 502
    if (d->connectionTimer.isActive()) {
        d->connectionTimer.stop();
    } else {
503 504
        if (QmlDebugConnection *connection = d->connection())
            connection->close();
hjk's avatar
hjk committed
505
    }
506 507
}

hjk's avatar
hjk committed
508 509
void QmlEngine::runEngine()
{
510 511 512 513 514 515
    // we won't get any debug output
    if (!terminal()) {
        d->retryOnConnectFail = true;
        d->automaticConnect = true;
    }

hjk's avatar
hjk committed
516
    QTC_ASSERT(state() == EngineRunRequested, qDebug() << state());
517

518
    if (!isSlaveEngine()) {
519
        if (runParameters().startMode == AttachToRemoteServer)
520
            tryToConnect();
521
        else if (runParameters().startMode == AttachToRemoteProcess)
522 523
            beginConnection();
        else
524
            startApplicationLauncher();
525
    } else {
526
        tryToConnect();
527
    }
528 529 530 531
}

void QmlEngine::startApplicationLauncher()
{
hjk's avatar
hjk committed
532
    if (!d->applicationLauncher.isRunning()) {
533
        StandardRunnable runnable = runParameters().inferior;
534 535 536 537
        runTool()->appendMessage(tr("Starting %1 %2").arg(
                                     QDir::toNativeSeparators(runnable.executable),
                                     runnable.commandLineArguments),
                                 Utils::NormalMessageFormat);
538
        d->applicationLauncher.start(runnable);
539
    }
540
}
541

542 543
void QmlEngine::stopApplicationLauncher()
{
hjk's avatar
hjk committed
544 545 546 547
    if (d->applicationLauncher.isRunning()) {
        disconnect(&d->applicationLauncher, &ApplicationLauncher::processExited,
                   this, &QmlEngine::disconnected);
        d->applicationLauncher.stop();
548
    }
549 550
}

hjk's avatar
hjk committed
551
void QmlEngine::shutdownInferior()
552
{
553
    CHECK_STATE(InferiorShutdownRequested);
hjk's avatar
hjk committed
554
    // End session.
555 556 557 558
    //    { "seq"     : <number>,
    //      "type"    : "request",
    //      "command" : "disconnect",
    //    }
559
    d->runCommand({DISCONNECT});
560

561
    if (isSlaveEngine())
562 563
        resetLocation();
    stopApplicationLauncher();
564
    closeConnection();
565

hjk's avatar
hjk committed
566 567 568 569 570
    notifyInferiorShutdownOk();
}

void QmlEngine::shutdownEngine()
{
hjk's avatar
hjk committed
571
    clearExceptionSelection();
572

hjk's avatar
hjk committed
573
    debuggerConsole()->setScriptEvaluator(ScriptEvaluator());
574 575

   // double check (ill engine?):
576
    stopApplicationLauncher();
577

578
    notifyEngineShutdownFinished();
579 580
    if (!isSlaveEngine())
        showMessage(QString(), StatusBar);
581 582
}

hjk's avatar
hjk committed
583
void QmlEngine::setupEngine()
584
{
585
    notifyEngineSetupOk();
586 587 588

    if (d->automaticConnect)
        beginConnection();
589 590
}

591 592
void QmlEngine::continueInferior()
{
hjk's avatar
hjk committed
593
    QTC_ASSERT(state() == InferiorStopOk, qDebug() << state());
hjk's avatar
hjk committed
594 595
    clearExceptionSelection();
    d->continueDebugging(Continue);
596
    resetLocation();
hjk's avatar
hjk committed
597 598
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
599 600 601 602
}

void QmlEngine::interruptInferior()
{
603
    showMessage(INTERRUPT, LogInput);
604
    d->runDirectCommand(INTERRUPT);
605
    showStatusMessage(tr("Waiting for JavaScript engine to interrupt on next statement."));
606 607 608 609
}

void QmlEngine::executeStep()
{
hjk's avatar
hjk committed
610 611
    clearExceptionSelection();
    d->continueDebugging(StepIn);
hjk's avatar
hjk committed
612 613
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
614 615 616 617
}

void QmlEngine::executeStepI()
{
hjk's avatar
hjk committed
618 619
    clearExceptionSelection();
    d->continueDebugging(StepIn);
hjk's avatar
hjk committed
620 621
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
622 623 624 625
}

void QmlEngine::executeStepOut()
{
hjk's avatar
hjk committed
626 627
    clearExceptionSelection();
    d->continueDebugging(StepOut);
hjk's avatar
hjk committed
628 629
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
630 631 632 633
}

void QmlEngine::executeNext()
{
hjk's avatar
hjk committed
634 635
    clearExceptionSelection();
    d->continueDebugging(Next);
hjk's avatar
hjk committed
636 637
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
638 639 640 641
}

void QmlEngine::executeNextI()
{
642
    executeNext();
643 644
}

645
void QmlEngine::executeRunToLine(const ContextData &data)
646
{
647
    QTC_ASSERT(state() == InferiorStopOk, qDebug() << state());
Jarek Kobus's avatar
Jarek Kobus committed
648
    showStatusMessage(tr("Run to line %1 (%2) requested...").arg(data.lineNumber).arg(data.fileName), 5000);
649
    d->setBreakpoint(SCRIPTREGEXP, data.fileName, true, data.lineNumber);
hjk's avatar
hjk committed
650 651 652
    clearExceptionSelection();
    d->continueDebugging(Continue);

653 654
    notifyInferiorRunRequested();
    notifyInferiorRunOk();
655 656 657 658 659 660 661 662
}

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

663
void QmlEngine::executeJumpToLine(const ContextData &data)
664
{
665
    Q_UNUSED(data)
666 667 668 669 670
    XSDEBUG("FIXME:  QmlEngine::executeJumpToLine()");
}

void QmlEngine::activateFrame(int index)
{
671 672 673
    if (state() != InferiorStopOk && state() != InferiorUnrunnable)
        return;

hjk's avatar
hjk committed
674
    stackHandler()->setCurrentIndex(index);
675
    gotoLocation(stackHandler()->frames().value(index));
676 677

    d->updateLocals();
678 679
}

680
void QmlEngine::selectThread(ThreadId threadId)
681
{
682
    Q_UNUSED(threadId)
683 684
}

685
void QmlEngine::insertBreakpoint(Breakpoint bp)
686
{
687 688 689
    BreakpointState state = bp.state();
    QTC_ASSERT(state == BreakpointInsertRequested, qDebug() << bp << this << state);
    bp.notifyBreakpointInsertProceeding();
690

691
    const BreakpointParameters &params = bp.parameters();
hjk's avatar
hjk committed
692
    if (params.type == BreakpointAtJavaScriptThrow) {
693 694 695
        BreakpointResponse br = bp.response();
        br.pending = false;
        bp.setResponse(br);
hjk's avatar
hjk committed
696 697 698 699
        bp.notifyBreakpointInsertOk();
        d->setExceptionBreak(AllExceptions, params.enabled);

    } else if (params.type == BreakpointByFileAndLine) {
700
        d->setBreakpoint(SCRIPTREGEXP, params.fileName,
701
                         params.enabled, params.lineNumber, 0,
702
                         params.condition, params.ignoreCount);
hjk's avatar
hjk committed
703 704

    } else if (params.type == BreakpointOnQmlSignalEmit) {
705
        d->setBreakpoint(EVENT, params.functionName, params.enabled);
706 707 708
        BreakpointResponse br = bp.response();
        br.pending = false;
        bp.setResponse(br);
hjk's avatar
hjk committed
709
        bp.notifyBreakpointInsertOk();
710
    }
hjk's avatar
hjk committed
711 712

    d->breakpointsSync.insert(d->sequence, bp.id());
713 714
}

715 716 717 718 719 720
void QmlEngine::resetLocation()
{
    DebuggerEngine::resetLocation();
    d->currentlyLookingUp.clear();
}

721
void QmlEngine::removeBreakpoint(Breakpoint bp)
722
{
723
    const BreakpointParameters &params = bp.parameters();
724

725 726 727
    BreakpointState state = bp.state();
    QTC_ASSERT(state == BreakpointRemoveRequested, qDebug() << bp << this << state);
    bp.notifyBreakpointRemoveProceeding();
728

hjk's avatar
hjk committed
729 730 731 732 733 734
    int breakpoint = d->breakpoints.value(bp.id());
    d->breakpoints.remove(bp.id());

    if (params.type == BreakpointAtJavaScriptThrow)
        d->setExceptionBreak(AllExceptions);
    else if (params.type == BreakpointOnQmlSignalEmit)
735
        d->setBreakpoint(EVENT, params.functionName, false);
hjk's avatar
hjk committed
736 737
    else
        d->clearBreakpoint(breakpoint);
738

739 740
    if (bp.state() == BreakpointRemoveProceeding)
        bp.notifyBreakpointRemoveOk();
741 742
}

743
void QmlEngine::changeBreakpoint(Breakpoint bp)
744
{
745 746 747
    BreakpointState state = bp.state();
    QTC_ASSERT(state == BreakpointChangeRequested, qDebug() << bp << this << state);
    bp.notifyBreakpointChangeProceeding();
748

hjk's avatar
hjk committed
749 750 751 752 753 754 755 756
    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) {
757
        d->setBreakpoint(EVENT, params.functionName, params.enabled);
hjk's avatar
hjk committed
758 759
        br.enabled = params.enabled;
        bp.setResponse(br);
760
    } else {
hjk's avatar
hjk committed
761 762 763 764 765 766
        //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);
767 768
    }

769 770
    if (bp.state() == BreakpointChangeProceeding)
        bp.notifyBreakpointChangeOk();
771 772
}

773 774
void QmlEngine::attemptBreakpointSynchronization()
{
775
    if (!stateAcceptsBreakpointChanges()) {
776
        showMessage("BREAKPOINT SYNCHRONIZATION NOT POSSIBLE IN CURRENT STATE");
777 778 779
        return;
    }

780
    BreakHandler *handler = breakHandler();
781

782
    DebuggerEngine *bpOwner = isSlaveEngine() ? masterEngine() : this;
783
    foreach (Breakpoint bp, handler->unclaimedBreakpoints()) {
784
        // Take ownership of the breakpoint. Requests insertion.
785 786
        if (acceptsBreakpoint(bp))
            bp.setEngine(bpOwner);
787 788
    }

789 790
    foreach (Breakpoint bp, handler->engineBreakpoints(bpOwner)) {
        switch (bp.state()) {
791 792 793 794 795
        case BreakpointNew:
            // Should not happen once claimed.
            QTC_CHECK(false);
            continue;
        case BreakpointInsertRequested:
796
            insertBreakpoint(bp);
797 798
            continue;
        case BreakpointChangeRequested:
799
            changeBreakpoint(bp);
800 801
            continue;
        case BreakpointRemoveRequested:
802
            removeBreakpoint(bp);
803 804 805 806 807 808 809
            continue;
        case BreakpointChangeProceeding:
        case BreakpointInsertProceeding:
        case BreakpointRemoveProceeding:
        case BreakpointInserted:
        case BreakpointDead:
            continue;
810
        }
811
        QTC_ASSERT(false, qDebug() << "UNKNOWN STATE"  << bp << state());
812 813
    }

814
    DebuggerEngine::attemptBreakpointSynchronization();
815 816
}

817
bool QmlEngine::acceptsBreakpoint(Breakpoint bp) const
818
{
819
    if (!bp.parameters().isCppBreakpoint())
820 821 822 823 824
            return true;

    //If it is a Cpp Breakpoint query if the type can be also handled by the debugger client
    //TODO: enable setting of breakpoints before start of debug session
    //For now, the event breakpoint can be set after the activeDebuggerClient is known
825
    //This is because the older client does not support BreakpointOnQmlSignalHandler
hjk's avatar
hjk committed
826 827 828 829
    BreakpointType type = bp.type();
    return type == BreakpointOnQmlSignalEmit
            || type == BreakpointByFileAndLine
            || type == BreakpointAtJavaScriptThrow;
830 831
}

832 833 834 835 836 837 838 839 840 841 842 843 844
void QmlEngine::loadSymbols(const QString &moduleName)
{
    Q_UNUSED(moduleName)
}

void QmlEngine::loadAllSymbols()
{
}

void QmlEngine::reloadModules()
{
}

845 846
void QmlEngine::reloadSourceFiles()
{
hjk's avatar
hjk committed
847
    d->scripts(4, QList<int>(), true, QVariant());
848 849
}

850 851 852 853 854
void QmlEngine::updateAll()
{
    d->updateLocals();
}

855 856 857 858 859
void QmlEngine::requestModuleSymbols(const QString &moduleName)
{
    Q_UNUSED(moduleName)
}

860
bool QmlEngine::canHandleToolTip(const DebuggerToolTipContext &) const
861
{
862
    // This is processed by QML inspector, which has dependencies to
hjk's avatar
hjk committed
863
    // the qml js editor. Makes life easier.
864
    // FIXME: Except that there isn't any attached.
865
    return true;
866 867
}

868
void QmlEngine::assignValueInDebugger(WatchItem *item,
869
    const QString &expression, const QVariant &editValue)
870
{
871
    if (!expression.isEmpty()) {
872 873 874 875
        QVariant value = (editValue.type() == QVariant::String)
                ? QVariant('"' + editValue.toString().replace('"', "\\\"") + '"')
                : editValue;

876 877
        if (item->isInspect()) {
            d->inspectorAgent.assignValue(item, expression, value);
hjk's avatar
hjk committed
878 879
        } else {
            StackHandler *handler = stackHandler();
880
            QString exp = QString("%1 = %2;").arg(expression).arg(value.toString());
hjk's avatar
hjk committed
881
            if (handler->isContentsValid() && handler->currentFrame().isUsable()) {
882
                d->evaluate(exp, -1, [this](const QVariantMap &) { d->updateLocals(); });
hjk's avatar
hjk committed
883
            } else {
884
                showMessage(tr("Cannot evaluate %1 in current stack frame.")
885
                            .arg(expression), ConsoleOutput);
hjk's avatar
hjk committed
886 887
            }
        }
888
    }
889 890
}

891
void QmlEngine::expandItem(const QString &iname)
892
{
893
    const WatchItem *item = watchHandler()->findItem(iname);
894
    QTC_ASSERT(item, return);
895

896
    if (item->isInspect()) {
897
        d->inspectorAgent.updateWatchData(*item);
898
    } else {
899
        LookupItems items;
900
        items.insert(int(item->id), {item->iname, item->name, item->exp});
901
        d->lookup(items);
902 903 904
    }
}

905
void QmlEngine::updateItem(const QString &iname)
906 907 908 909
{
    const WatchItem *item = watchHandler()->findItem(iname);
    QTC_ASSERT(item, return);

910 911 912
    if (state() == InferiorStopOk) {
        // The Qt side Q_ASSERTs otherwise. So postpone the evaluation,
        // it will be triggered from from upateLocals() later.
913
        QString exp = item->exp;
914
        d->evaluate(exp, -1, [this, iname, exp](const QVariantMap &response) {
915 916 917
            d->handleEvaluateExpression(response, iname, exp);
        });
    }
918 919
}

920
void QmlEngine::selectWatchData(const QString &iname)
921
{
922 923
    const WatchItem *item = watchHandler()->findItem(iname);
    if (item && item->isInspect())
924
        d->inspectorAgent.watchDataSelected(item->id);
925 926
}

927 928 929 930 931 932 933 934 935 936
bool compareConsoleItems(const ConsoleItem *a, const ConsoleItem *b)
{
    if (a == 0)
        return true;
    if (b == 0)
        return false;
    return a->text() < b->text();
}

static ConsoleItem *constructLogItemTree(const QVariant &result,
hjk's avatar
hjk committed
937
                                         const QString &key = QString())
938
{
hjk's avatar
hjk committed
939
    bool sorted = boolSetting(SortStructMembers);
940 941 942
    if (!result.isValid())
        return 0;

943 944
    QString text;
    ConsoleItem *item = 0;
945 946
    if (result.type() == QVariant::Map) {
        if (key.isEmpty())
947
            text = "Object";
948
        else
949
            text = key + " : Object";
950

951 952
        QMap<QString, QVariant> resultMap = result.toMap();
        QVarLengthArray<ConsoleItem *> children(resultMap.size());
953
        QMapIterator<QString, QVariant> i(result.toMap());
954
        auto it = children.begin();
955 956
        while (i.hasNext()) {
            i.next();
957 958 959 960 961 962 963 964 965 966
            *(it++) = constructLogItemTree(i.value(), i.key());
        }

        // Sort before inserting as ConsoleItem::sortChildren causes a whole cascade of changes we
        // may not want to handle here.
        if (sorted)
            std::sort(children.begin(), children.end(), compareConsoleItems);

        item = new ConsoleItem(ConsoleItem::DefaultType, text);
        foreach (ConsoleItem *child, children) {
967
            if (child)
968
                item->appendChild(child);
969
        }
970

971 972
    } else if (result.type() == QVariant::List) {
        if (key.isEmpty())
973
            text = "List";
974
        else
975
            text = QString("[%1] : List").arg(key);
976

977
        QVariantList resultList = result.toList();
978 979 980 981 982 983 984 985 986
        QVarLengthArray<ConsoleItem *> children(resultList.size());
        for (int i = 0; i < resultList.count(); i++)
            children[i] = constructLogItemTree(resultList.at(i), QString::number(i));

        if (sorted)
            std::sort(children.begin(), children.end(), compareConsoleItems);

        item = new ConsoleItem(ConsoleItem::DefaultType, text);
        foreach (ConsoleItem *child, children) {
987
            if (child)
988
                item->appendChild(child);
989 990
        }
    } else if (result.canConvert(QVariant::String)) {
991
        item = new ConsoleItem(ConsoleItem::DefaultType, result.toString());
992
    } else {
993
        item = new ConsoleItem(ConsoleItem::DefaultType, "Unknown Value");
994 995 996 997 998
    }

    return item;
}

999
void QmlEngine::expressionEvaluated(quint32 queryId, const QVariant &result)
1000
{
hjk's avatar
hjk committed
1001 1002
    if (d->queryIds.contains(queryId)) {
        d->queryIds.removeOne(queryId);
hjk's avatar
hjk committed
1003 1004
        if (ConsoleItem *item = constructLogItemTree(result))
            debuggerConsole()->printItem(item);
1005
    }
1006 1007
}

1008
bool QmlEngine::hasCapability(unsigned cap) const
1009
{
1010
    return cap & (AddWatcherCapability
1011
            | AddWatcherWhileRunningCapability
1012
            | RunToLineCapability);
1013 1014 1015 1016 1017 1018 1019 1020 1021
    /*ReverseSteppingCapability | SnapshotCapability
        | AutoDerefPointersCapability | DisassemblerCapability
        | RegisterCapability | ShowMemoryCapability
        | JumpToLineCapability | ReloadModuleCapability
        | ReloadModuleSymbolsCapability | BreakOnThrowAndCatchCapability
        | ReturnFromFunctionCapability
        | CreateFullBacktraceCapability
        | WatchpointCapability
        | AddWatcherCapability;*/
1022 1023
}

1024 1025
void QmlEngine::quitDebugger()
{
hjk's avatar
hjk committed
1026 1027
    d->automaticConnect = false;
    d->retryOnConnectFail = false;
1028 1029
    stopApplicationLauncher();
    closeConnection();
1030 1031
}

1032 1033 1034 1035 1036 1037
void QmlEngine::doUpdateLocals(const UpdateParameters &params)
{
    Q_UNUSED(params);
    d->updateLocals();
}

1038 1039
void QmlEngine::disconnected()
{
1040
    showMessage(tr("QML Debugger disconnected."), StatusBar);
1041 1042 1043
    notifyInferiorExited();
}

1044 1045
void QmlEngine::updateCurrentContext()
{
1046 1047
    d->inspectorAgent.enableTools(state() == InferiorRunOk);

1048
    QString context;
1049 1050
    switch (state()) {
    case InferiorStopOk:
1051
        context = stackHandler()->currentFrame().function;
1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074
        break;
    case InferiorRunOk:
        if (d->contextEvaluate || !d