Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ #include "qqmldebugserverfactory.h"
++
++ #include <private/qqmldebugserver_p.h>
++ #include <private/qqmldebugserverconnection_p.h>
++ #include <private/qqmldebugservice_p.h>
++ #include <private/qjsengine_p.h>
++ #include <private/qqmlglobal_p.h>
++ #include <private/qqmldebugpluginmanager_p.h>
++ #include <private/qqmldebugserviceinterfaces_p.h>
++ #include <private/qpacketprotocol_p.h>
++ #include <private/qversionedpacket_p.h>
++
++ #include <QtCore/QAtomicInt>
++ #include <QtCore/QDir>
++ #include <QtCore/QPluginLoader>
++ #include <QtCore/QStringList>
++ #include <QtCore/QList>
++ #include <QtCore/qwaitcondition.h>
++
++ QT_BEGIN_NAMESPACE
++
++ /*
++ QQmlDebug Protocol (Version 1):
++
++ handshake:
++ 1. Client sends
++ "QDeclarativeDebugServer" 0 version pluginNames [QDataStream version]
++ version: an int representing the highest protocol version the client knows
++ pluginNames: plugins available on client side
++ 2. Server sends
++ "QDeclarativeDebugClient" 0 version pluginNames pluginVersions [QDataStream version]
++ version: an int representing the highest protocol version the client & server know
++ pluginNames: plugins available on server side. plugins both in the client and server message are enabled.
++ client plugin advertisement
++ 1. Client sends
++ "QDeclarativeDebugServer" 1 pluginNames
++ server plugin advertisement (not implemented: all services are required to register before open())
++ 1. Server sends
++ "QDeclarativeDebugClient" 1 pluginNames pluginVersions
++ plugin communication:
++ Everything send with a header different to "QDeclarativeDebugServer" is sent to the appropriate plugin.
++ */
++
++ Q_QML_DEBUG_PLUGIN_LOADER(QQmlDebugServerConnection)
++
++ const int protocolVersion = 1;
++ using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>;
++
++ class QQmlDebugServerImpl;
++ class QQmlDebugServerThread : public QThread
++ {
++ public:
++ QQmlDebugServerThread() : m_server(nullptr), m_portFrom(-1), m_portTo(-1) {}
++
++ void setServer(QQmlDebugServerImpl *server)
++ {
++ m_server = server;
++ }
++
++ void setPortRange(int portFrom, int portTo, const QString &hostAddress)
++ {
++ m_pluginName = QLatin1String("QTcpServerConnection");
++ m_portFrom = portFrom;
++ m_portTo = portTo;
++ m_hostAddress = hostAddress;
++ }
++
++ void setFileName(const QString &fileName)
++ {
++ m_pluginName = QLatin1String("QLocalClientConnection");
++ m_fileName = fileName;
++ }
++
++ const QString &pluginName() const
++ {
++ return m_pluginName;
++ }
++
++ void run() override;
++
++ private:
++ QQmlDebugServerImpl *m_server;
++ QString m_pluginName;
++ int m_portFrom;
++ int m_portTo;
++ QString m_hostAddress;
++ QString m_fileName;
++ };
++
++ class QQmlDebugServerImpl : public QQmlDebugServer
++ {
++ Q_OBJECT
++ public:
++ QQmlDebugServerImpl();
++
++ bool blockingMode() const override;
++
++ QQmlDebugService *service(const QString &name) const override;
++
++ void addEngine(QJSEngine *engine) override;
++ void removeEngine(QJSEngine *engine) override;
++ bool hasEngine(QJSEngine *engine) const override;
++
++ bool addService(const QString &name, QQmlDebugService *service) override;
++ bool removeService(const QString &name) override;
++
++ bool open(const QVariantHash &configuration) override;
++ void setDevice(QIODevice *socket) override;
++
++ void parseArguments();
++
++ static void cleanup();
++
++ private:
++ friend class QQmlDebugServerThread;
++ friend class QQmlDebugServerFactory;
++
++ class EngineCondition {
++ public:
++ EngineCondition() : numServices(0), condition(new QWaitCondition) {}
++
++ bool waitForServices(QMutex *locked, int numEngines);
++ bool isWaiting() const { return numServices > 0; }
++
++ void wake();
++ private:
++ int numServices;
++
++ // shared pointer to allow for QHash-inflicted copying.
++ QSharedPointer<QWaitCondition> condition;
++ };
++
++ bool canSendMessage(const QString &name);
++ void doSendMessage(const QString &name, const QByteArray &message);
++ void wakeEngine(QJSEngine *engine);
++ void sendMessage(const QString &name, const QByteArray &message);
++ void sendMessages(const QString &name, const QList<QByteArray> &messages);
++ void changeServiceState(const QString &serviceName, QQmlDebugService::State state);
++ void removeThread();
++ void receiveMessage();
++ void protocolError();
++
++ QQmlDebugServerConnection *m_connection;
++ QHash<QString, QQmlDebugService *> m_plugins;
++ QStringList m_clientPlugins;
++ bool m_gotHello;
++ bool m_blockingMode;
++
++ QHash<QJSEngine *, EngineCondition> m_engineConditions;
++
++ mutable QMutex m_helloMutex;
++ QWaitCondition m_helloCondition;
++ QQmlDebugServerThread m_thread;
++ QPacketProtocol *m_protocol;
++ QAtomicInt m_changeServiceStateCalls;
++ };
++
++ void QQmlDebugServerImpl::cleanup()
++ {
++ QQmlDebugServerImpl *server = static_cast<QQmlDebugServerImpl *>(
++ QQmlDebugConnector::instance());
++ if (!server)
++ return;
++
++ {
++ QObject signalSource;
++ for (QHash<QString, QQmlDebugService *>::ConstIterator i = server->m_plugins.constBegin();
++ i != server->m_plugins.constEnd(); ++i) {
++ server->m_changeServiceStateCalls.ref();
++ QString key = i.key();
++ // Process this in the server's thread.
++ connect(&signalSource, &QObject::destroyed, server, [key, server](){
++ server->changeServiceState(key, QQmlDebugService::NotConnected);
++ }, Qt::QueuedConnection);
++ }
++ }
++
++ // Wait for changeServiceState calls to finish
++ // (while running an event loop because some services
++ // might again defer execution of stuff in the GUI thread)
++ QEventLoop loop;
++ while (!server->m_changeServiceStateCalls.testAndSetOrdered(0, 0))
++ loop.processEvents();
++
++ // Stop the thread while the application is still there.
++ server->m_thread.exit();
++ server->m_thread.wait();
++ }
++
++ void QQmlDebugServerThread::run()
++ {
++ Q_ASSERT_X(m_server != nullptr, Q_FUNC_INFO, "There should always be a debug server available here.");
++ QQmlDebugServerConnection *connection = loadQQmlDebugServerConnection(m_pluginName);
++ if (connection) {
++ {
++ QMutexLocker connectionLocker(&m_server->m_helloMutex);
++ m_server->m_connection = connection;
++ connection->setServer(m_server);
++ m_server->m_helloCondition.wakeAll();
++ }
++
++ if (m_fileName.isEmpty()) {
++ if (!connection->setPortRange(m_portFrom, m_portTo, m_server->blockingMode(),
++ m_hostAddress))
++ return;
++ } else if (!connection->setFileName(m_fileName, m_server->blockingMode())) {
++ return;
++ }
++
++ if (m_server->blockingMode())
++ connection->waitForConnection();
++ } else {
++ qWarning() << "QML Debugger: Couldn't load plugin" << m_pluginName;
++ return;
++ }
++
++ exec();
++
++ // make sure events still waiting are processed
++ QEventLoop eventLoop;
++ eventLoop.processEvents(QEventLoop::AllEvents);
++ }
++
++ bool QQmlDebugServerImpl::blockingMode() const
++ {
++ return m_blockingMode;
++ }
++
++ static void cleanupOnShutdown()
++ {
++ // We cannot do this in the destructor as the connection plugin will get unloaded before the
++ // server plugin and we need the connection to send any remaining data. This function is
++ // triggered before any plugins are unloaded.
++ QQmlDebugServerImpl::cleanup();
++ }
++
++ QQmlDebugServerImpl::QQmlDebugServerImpl() :
++ m_connection(nullptr),
++ m_gotHello(false),
++ m_blockingMode(false)
++ {
++ static bool postRoutineAdded = false;
++ if (!postRoutineAdded) {
++ qAddPostRoutine(cleanupOnShutdown);
++ postRoutineAdded = true;
++ }
++
++ // used in sendMessages
++ qRegisterMetaType<QList<QByteArray> >("QList<QByteArray>");
++ // used in changeServiceState
++ qRegisterMetaType<QQmlDebugService::State>("QQmlDebugService::State");
++
++ m_thread.setServer(this);
++ moveToThread(&m_thread);
++
++ // Remove the thread immmediately when it finishes, so that we don't have to wait for the
++ // event loop to signal that.
++ QObject::connect(&m_thread, &QThread::finished, this, &QQmlDebugServerImpl::removeThread,
++ Qt::DirectConnection);
++ m_thread.setObjectName(QStringLiteral("QQmlDebugServerThread"));
++ parseArguments();
++ }
++
++ bool QQmlDebugServerImpl::open(const QVariantHash &configuration = QVariantHash())
++ {
++ if (m_thread.isRunning())
++ return false;
++ if (!configuration.isEmpty()) {
++ m_blockingMode = configuration[QLatin1String("block")].toBool();
++ if (configuration.contains(QLatin1String("portFrom"))) {
++ int portFrom = configuration[QLatin1String("portFrom")].toInt();
++ int portTo = configuration[QLatin1String("portTo")].toInt();
++ m_thread.setPortRange(portFrom, portTo == -1 ? portFrom : portTo,
++ configuration[QLatin1String("hostAddress")].toString());
++ } else if (configuration.contains(QLatin1String("fileName"))) {
++ m_thread.setFileName(configuration[QLatin1String("fileName")].toString());
++ } else {
++ return false;
++ }
++ }
++
++ if (m_thread.pluginName().isEmpty())
++ return false;
++
++ QMutexLocker locker(&m_helloMutex);
++ m_thread.start();
++ m_helloCondition.wait(&m_helloMutex); // wait for connection
++ if (m_blockingMode && !m_gotHello)
++ m_helloCondition.wait(&m_helloMutex); // wait for hello
++ return true;
++ }
++
++ void QQmlDebugServerImpl::parseArguments()
++ {
++ // format: qmljsdebugger=port:<port_from>[,port_to],host:<ip address>][,block]
++ const QString args = commandLineArguments();
++ if (args.isEmpty())
++ return; // Manual initialization, through QQmlDebugServer::open()
++
++ // ### remove port definition when protocol is changed
++ int portFrom = 0;
++ int portTo = 0;
++ bool block = false;
++ bool ok = false;
++ QString hostAddress;
++ QString fileName;
++ QStringList services;
++
++ const auto lstjsDebugArguments = QStringView{args}.split(QLatin1Char(','), Qt::SkipEmptyParts);
++ for (auto argsIt = lstjsDebugArguments.begin(), argsItEnd = lstjsDebugArguments.end(); argsIt != argsItEnd; ++argsIt) {
++ const QStringView &strArgument = *argsIt;
++ if (strArgument.startsWith(QLatin1String("port:"))) {
++ portFrom = strArgument.mid(5).toInt(&ok);
++ portTo = portFrom;
++ const auto argsNext = argsIt + 1;
++ if (argsNext == argsItEnd)
++ break;
++ if (ok) {
++ portTo = argsNext->toString().toInt(&ok);
++ if (ok) {
++ ++argsIt;
++ } else {
++ portTo = portFrom;
++ ok = true;
++ }
++ }
++ } else if (strArgument.startsWith(QLatin1String("host:"))) {
++ hostAddress = strArgument.mid(5).toString();
++ } else if (strArgument == QLatin1String("block")) {
++ block = true;
++ } else if (strArgument.startsWith(QLatin1String("file:"))) {
++ fileName = strArgument.mid(5).toString();
++ ok = !fileName.isEmpty();
++ } else if (strArgument.startsWith(QLatin1String("services:"))) {
++ services.append(strArgument.mid(9).toString());
++ } else if (!services.isEmpty()) {
++ services.append(strArgument.toString());
++ } else if (!strArgument.startsWith(QLatin1String("connector:"))) {
++ const QString message = tr("QML Debugger: Invalid argument \"%1\" detected."
++ " Ignoring the same.").arg(strArgument.toString());
++ qWarning("%s", qPrintable(message));
++ }
++ }
++
++ if (ok) {
++ setServices(services);
++ m_blockingMode = block;
++ if (!fileName.isEmpty())
++ m_thread.setFileName(fileName);
++ else
++ m_thread.setPortRange(portFrom, portTo, hostAddress);
++ } else {
++ QString usage;
++ QTextStream str(&usage);
++ str << tr("QML Debugger: Ignoring \"-qmljsdebugger=%1\".").arg(args) << '\n'
++ << tr("The format is \"-qmljsdebugger=[file:<file>|port:<port_from>][,<port_to>]"
++ "[,host:<ip address>][,block][,services:<service>][,<service>]*\"") << '\n'
++ << tr("\"file:\" can be used to specify the name of a file the debugger will try "
++ "to connect to using a QLocalSocket. If \"file:\" is given any \"host:\" and"
++ "\"port:\" arguments will be ignored.") << '\n'
++ << tr("\"host:\" and \"port:\" can be used to specify an address and a single "
++ "port or a range of ports the debugger will try to bind to with a "
++ "QTcpServer.") << '\n'
++ << tr("\"block\" makes the debugger and some services wait for clients to be "
++ "connected and ready before the first QML engine starts.") << '\n'
++ << tr("\"services:\" can be used to specify which debug services the debugger "
++ "should load. Some debug services interact badly with others. The V4 "
++ "debugger should not be loaded when using the QML profiler as it will force "
++ "any V4 engines to use the JavaScript interpreter rather than the JIT. The "
++ "following debug services are available by default:") << '\n'
++ << QQmlEngineDebugService::s_key << "\t- " << tr("The QML debugger") << '\n'
++ << QV4DebugService::s_key << "\t- " << tr("The V4 debugger") << '\n'
++ << QQmlInspectorService::s_key << "\t- " << tr("The QML inspector") << '\n'
++ << QQmlProfilerService::s_key << "\t- " << tr("The QML profiler") << '\n'
++ << QQmlEngineControlService::s_key << "\t- "
++ //: Please preserve the line breaks and formatting
++ << tr("Allows the client to delay the starting and stopping of\n"
++ "\t\t QML engines until other services are ready. QtCreator\n"
++ "\t\t uses this service with the QML profiler in order to\n"
++ "\t\t profile multiple QML engines at the same time.")
++ << '\n' << QDebugMessageService::s_key << "\t- "
++ //: Please preserve the line breaks and formatting
++ << tr("Sends qDebug() and similar messages over the QML debug\n"
++ "\t\t connection. QtCreator uses this for showing debug\n"
++ "\t\t messages in the debugger console.") << '\n'
++ #if QT_CONFIG(translation)
++ << '\n' << QQmlDebugTranslationService::s_key << "\t- "
++ //: Please preserve the line breaks and formatting
++ << tr("helps to see if a translated text\n"
++ "\t\t will result in an elided text\n"
++ "\t\t in QML elements.") << '\n'
++ #endif //QT_CONFIG(translation)
++ << tr("Other services offered by qmltooling plugins that implement "
++ "QQmlDebugServiceFactory and which can be found in the standard plugin "
++ "paths will also be available and can be specified. If no \"services\" "
++ "argument is given, all services found this way, including the default "
++ "ones, are loaded.");
++ qWarning("%s", qPrintable(usage));
++ }
++ }
++
++ void QQmlDebugServerImpl::receiveMessage()
++ {
++ typedef QHash<QString, QQmlDebugService*>::const_iterator DebugServiceConstIt;
++
++ // to be executed in debugger thread
++ Q_ASSERT(QThread::currentThread() == thread());
++
++ if (!m_protocol)
++ return;
++
++ QQmlDebugPacket in(m_protocol->read());
++
++ QString name;
++
++ in >> name;
++ if (name == QLatin1String("QDeclarativeDebugServer")) {
++ int op = -1;
++ in >> op;
++ if (op == 0) {
++ int version;
++ in >> version >> m_clientPlugins;
++
++ //Get the supported QDataStream version
++ if (!in.atEnd()) {
++ in >> s_dataStreamVersion;
++ if (s_dataStreamVersion > QDataStream::Qt_DefaultCompiledVersion)
++ s_dataStreamVersion = QDataStream::Qt_DefaultCompiledVersion;
++ }
++
++ bool clientSupportsMultiPackets = false;
++ if (!in.atEnd())
++ in >> clientSupportsMultiPackets;
++
++ // Send the hello answer immediately, since it needs to arrive before
++ // the plugins below start sending messages.
++
++ QQmlDebugPacket out;
++ QStringList pluginNames;
++ QList<float> pluginVersions;
++ if (clientSupportsMultiPackets) { // otherwise, disable all plugins
++ const int count = m_plugins.size();
++ pluginNames.reserve(count);
++ pluginVersions.reserve(count);
++ for (QHash<QString, QQmlDebugService *>::ConstIterator i = m_plugins.constBegin();
++ i != m_plugins.constEnd(); ++i) {
++ pluginNames << i.key();
++ pluginVersions << i.value()->version();
++ }
++ }
++
++ out << QString(QStringLiteral("QDeclarativeDebugClient")) << 0 << protocolVersion
++ << pluginNames << pluginVersions << dataStreamVersion();
++
++ m_protocol->send(out.data());
++ m_connection->flush();
++
++ QMutexLocker helloLock(&m_helloMutex);
++ m_gotHello = true;
++
++ for (DebugServiceConstIt iter = m_plugins.constBegin(), cend = m_plugins.constEnd(); iter != cend; ++iter) {
++ QQmlDebugService::State newState = QQmlDebugService::Unavailable;
++ if (m_clientPlugins.contains(iter.key()))
++ newState = QQmlDebugService::Enabled;
++ m_changeServiceStateCalls.ref();
++ changeServiceState(iter.key(), newState);
++ }
++
++ m_helloCondition.wakeAll();
++
++ } else if (op == 1) {
++ // Service Discovery
++ QStringList oldClientPlugins = m_clientPlugins;
++ in >> m_clientPlugins;
++
++ for (DebugServiceConstIt iter = m_plugins.constBegin(), cend = m_plugins.constEnd(); iter != cend; ++iter) {
++ const QString &pluginName = iter.key();
++ QQmlDebugService::State newState = QQmlDebugService::Unavailable;
++ if (m_clientPlugins.contains(pluginName))
++ newState = QQmlDebugService::Enabled;
++
++ if (oldClientPlugins.contains(pluginName)
++ != m_clientPlugins.contains(pluginName)) {
++ m_changeServiceStateCalls.ref();
++ changeServiceState(iter.key(), newState);
++ }
++ }
++
++ } else {
++ qWarning("QML Debugger: Invalid control message %d.", op);
++ protocolError();
++ return;
++ }
++
++ } else {
++ if (m_gotHello) {
++ const auto iter = m_plugins.constFind(name);
++ if (iter == m_plugins.cend()) {
++ qWarning() << "QML Debugger: Message received for missing plugin" << name << '.';
++ } else {
++ QQmlDebugService *service = *iter;
++ QByteArray message;
++ while (!in.atEnd()) {
++ in >> message;
++ service->messageReceived(message);
++ }
++ }
++ } else {
++ qWarning("QML Debugger: Invalid hello message.");
++ }
++
++ }
++ }
++
++ void QQmlDebugServerImpl::changeServiceState(const QString &serviceName,
++ QQmlDebugService::State newState)
++ {
++ // to be executed in debugger thread
++ Q_ASSERT(QThread::currentThread() == thread());
++
++ QQmlDebugService *service = m_plugins.value(serviceName);
++ if (service && service->state() != newState) {
++ service->stateAboutToBeChanged(newState);
++ service->setState(newState);
++ service->stateChanged(newState);
++ }
++
++ m_changeServiceStateCalls.deref();
++ }
++
++ void QQmlDebugServerImpl::removeThread()
++ {
++ Q_ASSERT(m_thread.isFinished());
++ Q_ASSERT(QThread::currentThread() == thread());
++
++ QThread *parentThread = m_thread.thread();
++
++ delete m_connection;
++ m_connection = nullptr;
++
++ // Move it back to the parent thread so that we can potentially restart it on a new thread.
++ moveToThread(parentThread);
++ }
++
++ QQmlDebugService *QQmlDebugServerImpl::service(const QString &name) const
++ {
++ return m_plugins.value(name);
++ }
++
++ void QQmlDebugServerImpl::addEngine(QJSEngine *engine)
++ {
++ // to be executed outside of debugger thread
++ Q_ASSERT(QThread::currentThread() != &m_thread);
++
++ QMutexLocker locker(&m_helloMutex);
++ Q_ASSERT(!m_engineConditions.contains(engine));
++
++ for (QQmlDebugService *service : std::as_const(m_plugins))
++ service->engineAboutToBeAdded(engine);
++
++ m_engineConditions[engine].waitForServices(&m_helloMutex, m_plugins.size());
++
++ for (QQmlDebugService *service : std::as_const(m_plugins))
++ service->engineAdded(engine);
++ }
++
++ void QQmlDebugServerImpl::removeEngine(QJSEngine *engine)
++ {
++ // to be executed outside of debugger thread
++ Q_ASSERT(QThread::currentThread() != &m_thread);
++
++ QMutexLocker locker(&m_helloMutex);
++ Q_ASSERT(m_engineConditions.contains(engine));
++
++ for (QQmlDebugService *service : std::as_const(m_plugins))
++ service->engineAboutToBeRemoved(engine);
++
++ m_engineConditions[engine].waitForServices(&m_helloMutex, m_plugins.size());
++
++ for (QQmlDebugService *service : std::as_const(m_plugins))
++ service->engineRemoved(engine);
++
++ m_engineConditions.remove(engine);
++ }
++
++ bool QQmlDebugServerImpl::hasEngine(QJSEngine *engine) const
++ {
++ QMutexLocker locker(&m_helloMutex);
++ QHash<QJSEngine *, EngineCondition>::ConstIterator i = m_engineConditions.constFind(engine);
++ // if we're still waiting the engine isn't fully "there", yet, nor fully removed.
++ return i != m_engineConditions.constEnd() && !i.value().isWaiting();
++ }
++
++ bool QQmlDebugServerImpl::addService(const QString &name, QQmlDebugService *service)
++ {
++ // to be executed before thread starts
++ Q_ASSERT(!m_thread.isRunning());
++
++ if (!service || m_plugins.contains(name))
++ return false;
++
++ connect(service, &QQmlDebugService::messageToClient,
++ this, &QQmlDebugServerImpl::sendMessage);
++ connect(service, &QQmlDebugService::messagesToClient,
++ this, &QQmlDebugServerImpl::sendMessages);
++
++ connect(service, &QQmlDebugService::attachedToEngine,
++ this, &QQmlDebugServerImpl::wakeEngine, Qt::QueuedConnection);
++ connect(service, &QQmlDebugService::detachedFromEngine,
++ this, &QQmlDebugServerImpl::wakeEngine, Qt::QueuedConnection);
++
++ service->setState(QQmlDebugService::Unavailable);
++ m_plugins.insert(name, service);
++
++ return true;
++ }
++
++ bool QQmlDebugServerImpl::removeService(const QString &name)
++ {
++ if (m_thread.isRunning())
++ qFatal("QML debugging framework was not cleaned up. Did you leak your QCoreApplication?");
++ // to be executed after thread ends
++ Q_ASSERT(!m_thread.isRunning());
++
++ QQmlDebugService *service = m_plugins.value(name);
++ if (!service)
++ return false;
++
++ m_plugins.remove(name);
++ service->setState(QQmlDebugService::NotConnected);
++
++ disconnect(service, &QQmlDebugService::detachedFromEngine,
++ this, &QQmlDebugServerImpl::wakeEngine);
++ disconnect(service, &QQmlDebugService::attachedToEngine,
++ this, &QQmlDebugServerImpl::wakeEngine);
++
++ disconnect(service, &QQmlDebugService::messagesToClient,
++ this, &QQmlDebugServerImpl::sendMessages);
++ disconnect(service, &QQmlDebugService::messageToClient,
++ this, &QQmlDebugServerImpl::sendMessage);
++
++ return true;
++ }
++
++ bool QQmlDebugServerImpl::canSendMessage(const QString &name)
++ {
++ // to be executed in debugger thread
++ Q_ASSERT(QThread::currentThread() == thread());
++ return m_connection && m_connection->isConnected() && m_protocol &&
++ m_clientPlugins.contains(name);
++ }
++
++ void QQmlDebugServerImpl::doSendMessage(const QString &name, const QByteArray &message)
++ {
++ QQmlDebugPacket out;
++ out << name << message;
++ m_protocol->send(out.data());
++ }
++
++ void QQmlDebugServerImpl::sendMessage(const QString &name, const QByteArray &message)
++ {
++ if (canSendMessage(name)) {
++ doSendMessage(name, message);
++ m_connection->flush();
++ }
++ }
++
++ void QQmlDebugServerImpl::sendMessages(const QString &name, const QList<QByteArray> &messages)
++ {
++ if (canSendMessage(name)) {
++ QQmlDebugPacket out;
++ out << name;
++ for (const QByteArray &message : messages)
++ out << message;
++ m_protocol->send(out.data());
++ m_connection->flush();
++ }
++ }
++
++ void QQmlDebugServerImpl::wakeEngine(QJSEngine *engine)
++ {
++ // to be executed in debugger thread
++ Q_ASSERT(QThread::currentThread() == thread());
++
++ QMutexLocker locker(&m_helloMutex);
++ m_engineConditions[engine].wake();
++ }
++
++ bool QQmlDebugServerImpl::EngineCondition::waitForServices(QMutex *locked, int num)
++ {
++ Q_ASSERT_X(numServices == 0, Q_FUNC_INFO, "Request to wait again before previous wait finished");
++ numServices = num;
++ return numServices > 0 ? condition->wait(locked) : true;
++ }
++
++ void QQmlDebugServerImpl::EngineCondition::wake()
++ {
++ if (--numServices == 0)
++ condition->wakeAll();
++ Q_ASSERT_X(numServices >=0, Q_FUNC_INFO, "Woken more often than #services.");
++ }
++
++ void QQmlDebugServerImpl::setDevice(QIODevice *socket)
++ {
++ m_protocol = new QPacketProtocol(socket, this);
++ QObject::connect(m_protocol, &QPacketProtocol::readyRead,
++ this, &QQmlDebugServerImpl::receiveMessage);
++ QObject::connect(m_protocol, &QPacketProtocol::error,
++ this, &QQmlDebugServerImpl::protocolError);
++
++ if (blockingMode())
++ m_protocol->waitForReadyRead(-1);
++ }
++
++ void QQmlDebugServerImpl::protocolError()
++ {
++ qWarning("QML Debugger: A protocol error has occurred! Giving up ...");
++ m_connection->disconnect();
++ // protocol might still be processing packages at this point
++ m_protocol->deleteLater();
++ m_protocol = nullptr;
++ }
++
++ QQmlDebugConnector *QQmlDebugServerFactory::create(const QString &key)
++ {
++ // Cannot parent it to this because it gets moved to another thread
++ return (key == QLatin1String("QQmlDebugServer") ? new QQmlDebugServerImpl : nullptr);
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qqmldebugserverfactory.cpp"
++ #include "qqmldebugserverfactory.moc"