1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qqmldebugserverfactory.h"
41 
42 #include <private/qqmldebugserver_p.h>
43 #include <private/qqmldebugserverconnection_p.h>
44 #include <private/qqmldebugservice_p.h>
45 #include <private/qjsengine_p.h>
46 #include <private/qqmlglobal_p.h>
47 #include <private/qqmldebugpluginmanager_p.h>
48 #include <private/qqmldebugserviceinterfaces_p.h>
49 #include <private/qpacketprotocol_p.h>
50 #include <private/qversionedpacket_p.h>
51 
52 #include <QtCore/QAtomicInt>
53 #include <QtCore/QDir>
54 #include <QtCore/QPluginLoader>
55 #include <QtCore/QStringList>
56 #include <QtCore/QVector>
57 #include <QtCore/qwaitcondition.h>
58 
59 QT_BEGIN_NAMESPACE
60 
61 /*
62   QQmlDebug Protocol (Version 1):
63 
64   handshake:
65     1. Client sends
66          "QDeclarativeDebugServer" 0 version pluginNames [QDataStream version]
67        version: an int representing the highest protocol version the client knows
68        pluginNames: plugins available on client side
69     2. Server sends
70          "QDeclarativeDebugClient" 0 version pluginNames pluginVersions [QDataStream version]
71        version: an int representing the highest protocol version the client & server know
72        pluginNames: plugins available on server side. plugins both in the client and server message are enabled.
73   client plugin advertisement
74     1. Client sends
75          "QDeclarativeDebugServer" 1 pluginNames
76   server plugin advertisement (not implemented: all services are required to register before open())
77     1. Server sends
78          "QDeclarativeDebugClient" 1 pluginNames pluginVersions
79   plugin communication:
80        Everything send with a header different to "QDeclarativeDebugServer" is sent to the appropriate plugin.
81   */
82 
83 Q_QML_DEBUG_PLUGIN_LOADER(QQmlDebugServerConnection)
84 
85 const int protocolVersion = 1;
86 using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>;
87 
88 class QQmlDebugServerImpl;
89 class QQmlDebugServerThread : public QThread
90 {
91 public:
QQmlDebugServerThread()92     QQmlDebugServerThread() : m_server(nullptr), m_portFrom(-1), m_portTo(-1) {}
93 
setServer(QQmlDebugServerImpl * server)94     void setServer(QQmlDebugServerImpl *server)
95     {
96         m_server = server;
97     }
98 
setPortRange(int portFrom,int portTo,const QString & hostAddress)99     void setPortRange(int portFrom, int portTo, const QString &hostAddress)
100     {
101         m_pluginName = QLatin1String("QTcpServerConnection");
102         m_portFrom = portFrom;
103         m_portTo = portTo;
104         m_hostAddress = hostAddress;
105     }
106 
setFileName(const QString & fileName)107     void setFileName(const QString &fileName)
108     {
109         m_pluginName = QLatin1String("QLocalClientConnection");
110         m_fileName = fileName;
111     }
112 
pluginName() const113     const QString &pluginName() const
114     {
115         return m_pluginName;
116     }
117 
118     void run() override;
119 
120 private:
121     QQmlDebugServerImpl *m_server;
122     QString m_pluginName;
123     int m_portFrom;
124     int m_portTo;
125     QString m_hostAddress;
126     QString m_fileName;
127 };
128 
129 class QQmlDebugServerImpl : public QQmlDebugServer
130 {
131     Q_OBJECT
132 public:
133     QQmlDebugServerImpl();
134 
135     bool blockingMode() const override;
136 
137     QQmlDebugService *service(const QString &name) const override;
138 
139     void addEngine(QJSEngine *engine) override;
140     void removeEngine(QJSEngine *engine) override;
141     bool hasEngine(QJSEngine *engine) const override;
142 
143     bool addService(const QString &name, QQmlDebugService *service) override;
144     bool removeService(const QString &name) override;
145 
146     bool open(const QVariantHash &configuration) override;
147     void setDevice(QIODevice *socket) override;
148 
149     void parseArguments();
150 
151     static void cleanup();
152 
153 private:
154     friend class QQmlDebugServerThread;
155     friend class QQmlDebugServerFactory;
156 
157     class EngineCondition {
158     public:
EngineCondition()159         EngineCondition() : numServices(0), condition(new QWaitCondition) {}
160 
161         bool waitForServices(QMutex *locked, int numEngines);
isWaiting() const162         bool isWaiting() const { return numServices > 0; }
163 
164         void wake();
165     private:
166         int numServices;
167 
168         // shared pointer to allow for QHash-inflicted copying.
169         QSharedPointer<QWaitCondition> condition;
170     };
171 
172     bool canSendMessage(const QString &name);
173     void doSendMessage(const QString &name, const QByteArray &message);
174     void wakeEngine(QJSEngine *engine);
175     void sendMessage(const QString &name, const QByteArray &message);
176     void sendMessages(const QString &name, const QList<QByteArray> &messages);
177     void changeServiceState(const QString &serviceName, QQmlDebugService::State state);
178     void removeThread();
179     void receiveMessage();
180     void protocolError();
181 
182     QQmlDebugServerConnection *m_connection;
183     QHash<QString, QQmlDebugService *> m_plugins;
184     QStringList m_clientPlugins;
185     bool m_gotHello;
186     bool m_blockingMode;
187 
188     QHash<QJSEngine *, EngineCondition> m_engineConditions;
189 
190     mutable QMutex m_helloMutex;
191     QWaitCondition m_helloCondition;
192     QQmlDebugServerThread m_thread;
193     QPacketProtocol *m_protocol;
194     QAtomicInt m_changeServiceStateCalls;
195 };
196 
cleanup()197 void QQmlDebugServerImpl::cleanup()
198 {
199     QQmlDebugServerImpl *server = static_cast<QQmlDebugServerImpl *>(
200                 QQmlDebugConnector::instance());
201     if (!server)
202         return;
203 
204     {
205         QObject signalSource;
206         for (QHash<QString, QQmlDebugService *>::ConstIterator i = server->m_plugins.constBegin();
207              i != server->m_plugins.constEnd(); ++i) {
208             server->m_changeServiceStateCalls.ref();
209             QString key = i.key();
210             // Process this in the server's thread.
211             connect(&signalSource, &QObject::destroyed, server, [key, server](){
212                 server->changeServiceState(key, QQmlDebugService::NotConnected);
213             }, Qt::QueuedConnection);
214         }
215     }
216 
217     // Wait for changeServiceState calls to finish
218     // (while running an event loop because some services
219     // might again defer execution of stuff in the GUI thread)
220     QEventLoop loop;
221     while (!server->m_changeServiceStateCalls.testAndSetOrdered(0, 0))
222         loop.processEvents();
223 
224     // Stop the thread while the application is still there.
225     server->m_thread.exit();
226     server->m_thread.wait();
227 }
228 
run()229 void QQmlDebugServerThread::run()
230 {
231     Q_ASSERT_X(m_server != nullptr, Q_FUNC_INFO, "There should always be a debug server available here.");
232     QQmlDebugServerConnection *connection = loadQQmlDebugServerConnection(m_pluginName);
233     if (connection) {
234         {
235             QMutexLocker connectionLocker(&m_server->m_helloMutex);
236             m_server->m_connection = connection;
237             connection->setServer(m_server);
238             m_server->m_helloCondition.wakeAll();
239         }
240 
241         if (m_fileName.isEmpty()) {
242             if (!connection->setPortRange(m_portFrom, m_portTo, m_server->blockingMode(),
243                                           m_hostAddress))
244                 return;
245         } else if (!connection->setFileName(m_fileName, m_server->blockingMode())) {
246             return;
247         }
248 
249         if (m_server->blockingMode())
250             connection->waitForConnection();
251     } else {
252         qWarning() << "QML Debugger: Couldn't load plugin" << m_pluginName;
253         return;
254     }
255 
256     exec();
257 
258     // make sure events still waiting are processed
259     QEventLoop eventLoop;
260     eventLoop.processEvents(QEventLoop::AllEvents);
261 }
262 
blockingMode() const263 bool QQmlDebugServerImpl::blockingMode() const
264 {
265     return m_blockingMode;
266 }
267 
cleanupOnShutdown()268 static void cleanupOnShutdown()
269 {
270     // We cannot do this in the destructor as the connection plugin will get unloaded before the
271     // server plugin and we need the connection to send any remaining data. This function is
272     // triggered before any plugins are unloaded.
273     QQmlDebugServerImpl::cleanup();
274 }
275 
QQmlDebugServerImpl()276 QQmlDebugServerImpl::QQmlDebugServerImpl() :
277     m_connection(nullptr),
278     m_gotHello(false),
279     m_blockingMode(false)
280 {
281     static bool postRoutineAdded = false;
282     if (!postRoutineAdded) {
283         qAddPostRoutine(cleanupOnShutdown);
284         postRoutineAdded = true;
285     }
286 
287     // used in sendMessages
288     qRegisterMetaType<QList<QByteArray> >("QList<QByteArray>");
289     // used in changeServiceState
290     qRegisterMetaType<QQmlDebugService::State>("QQmlDebugService::State");
291 
292     m_thread.setServer(this);
293     moveToThread(&m_thread);
294 
295     // Remove the thread immmediately when it finishes, so that we don't have to wait for the
296     // event loop to signal that.
297     QObject::connect(&m_thread, &QThread::finished, this, &QQmlDebugServerImpl::removeThread,
298                      Qt::DirectConnection);
299     m_thread.setObjectName(QStringLiteral("QQmlDebugServerThread"));
300     parseArguments();
301 }
302 
open(const QVariantHash & configuration=QVariantHash ())303 bool QQmlDebugServerImpl::open(const QVariantHash &configuration = QVariantHash())
304 {
305     if (m_thread.isRunning())
306         return false;
307     if (!configuration.isEmpty()) {
308         m_blockingMode = configuration[QLatin1String("block")].toBool();
309         if (configuration.contains(QLatin1String("portFrom"))) {
310             int portFrom = configuration[QLatin1String("portFrom")].toInt();
311             int portTo = configuration[QLatin1String("portTo")].toInt();
312             m_thread.setPortRange(portFrom, portTo == -1 ? portFrom : portTo,
313                                    configuration[QLatin1String("hostAddress")].toString());
314         } else if (configuration.contains(QLatin1String("fileName"))) {
315             m_thread.setFileName(configuration[QLatin1String("fileName")].toString());
316         } else {
317             return false;
318         }
319     }
320 
321     if (m_thread.pluginName().isEmpty())
322         return false;
323 
324     QMutexLocker locker(&m_helloMutex);
325     m_thread.start();
326     m_helloCondition.wait(&m_helloMutex); // wait for connection
327     if (m_blockingMode && !m_gotHello)
328         m_helloCondition.wait(&m_helloMutex); // wait for hello
329     return true;
330 }
331 
parseArguments()332 void QQmlDebugServerImpl::parseArguments()
333 {
334     // format: qmljsdebugger=port:<port_from>[,port_to],host:<ip address>][,block]
335     const QString args = commandLineArguments();
336     if (args.isEmpty())
337         return; // Manual initialization, through QQmlDebugServer::open()
338 
339     // ### remove port definition when protocol is changed
340     int portFrom = 0;
341     int portTo = 0;
342     bool block = false;
343     bool ok = false;
344     QString hostAddress;
345     QString fileName;
346     QStringList services;
347 
348     const auto lstjsDebugArguments = args.splitRef(QLatin1Char(','), Qt::SkipEmptyParts);
349     for (auto argsIt = lstjsDebugArguments.begin(), argsItEnd = lstjsDebugArguments.end(); argsIt != argsItEnd; ++argsIt) {
350         const QStringRef &strArgument = *argsIt;
351         if (strArgument.startsWith(QLatin1String("port:"))) {
352             portFrom = strArgument.mid(5).toInt(&ok);
353             portTo = portFrom;
354             const auto argsNext = argsIt + 1;
355             if (argsNext == argsItEnd)
356                 break;
357             if (ok) {
358                 portTo = argsNext->toString().toInt(&ok);
359                 if (ok) {
360                     ++argsIt;
361                 } else {
362                     portTo = portFrom;
363                     ok = true;
364                 }
365             }
366         } else if (strArgument.startsWith(QLatin1String("host:"))) {
367             hostAddress = strArgument.mid(5).toString();
368         } else if (strArgument == QLatin1String("block")) {
369             block = true;
370         } else if (strArgument.startsWith(QLatin1String("file:"))) {
371             fileName = strArgument.mid(5).toString();
372             ok = !fileName.isEmpty();
373         } else if (strArgument.startsWith(QLatin1String("services:"))) {
374             services.append(strArgument.mid(9).toString());
375         } else if (!services.isEmpty()) {
376             services.append(strArgument.toString());
377         } else if (!strArgument.startsWith(QLatin1String("connector:"))) {
378             const QString message = tr("QML Debugger: Invalid argument \"%1\" detected."
379                                        " Ignoring the same.").arg(strArgument.toString());
380             qWarning("%s", qPrintable(message));
381         }
382     }
383 
384     if (ok) {
385         setServices(services);
386         m_blockingMode = block;
387         if (!fileName.isEmpty())
388             m_thread.setFileName(fileName);
389         else
390             m_thread.setPortRange(portFrom, portTo, hostAddress);
391     } else {
392         QString usage;
393         QTextStream str(&usage);
394         str << tr("QML Debugger: Ignoring \"-qmljsdebugger=%1\".").arg(args) << '\n'
395             << tr("The format is \"-qmljsdebugger=[file:<file>|port:<port_from>][,<port_to>]"
396                   "[,host:<ip address>][,block][,services:<service>][,<service>]*\"") << '\n'
397             << tr("\"file:\" can be used to specify the name of a file the debugger will try "
398                   "to connect to using a QLocalSocket. If \"file:\" is given any \"host:\" and"
399                   "\"port:\" arguments will be ignored.") << '\n'
400             << tr("\"host:\" and \"port:\" can be used to specify an address and a single "
401                   "port or a range of ports the debugger will try to bind to with a "
402                   "QTcpServer.") << '\n'
403             << tr("\"block\" makes the debugger and some services wait for clients to be "
404                   "connected and ready before the first QML engine starts.") << '\n'
405             << tr("\"services:\" can be used to specify which debug services the debugger "
406                   "should load. Some debug services interact badly with others. The V4 "
407                   "debugger should not be loaded when using the QML profiler as it will force "
408                   "any V4 engines to use the JavaScript interpreter rather than the JIT. The "
409                   "following debug services are available by default:") << '\n'
410             << QQmlEngineDebugService::s_key   << "\t- " << tr("The QML debugger") << '\n'
411             << QV4DebugService::s_key          << "\t- " << tr("The V4 debugger") << '\n'
412             << QQmlInspectorService::s_key     << "\t- " << tr("The QML inspector") << '\n'
413             << QQmlProfilerService::s_key      << "\t- " << tr("The QML profiler") << '\n'
414             << QQmlEngineControlService::s_key << "\t- "
415             //: Please preserve the line breaks and formatting
416             << tr("Allows the client to delay the starting and stopping of\n"
417                   "\t\t  QML engines until other services are ready. QtCreator\n"
418                   "\t\t  uses this service with the QML profiler in order to\n"
419                   "\t\t  profile multiple QML engines at the same time.")
420             << '\n' << QDebugMessageService::s_key << "\t- "
421             //: Please preserve the line breaks and formatting
422             << tr("Sends qDebug() and similar messages over the QML debug\n"
423                "\t\t  connection. QtCreator uses this for showing debug\n"
424                "\t\t  messages in the debugger console.") << '\n'
425             << '\n' << QQmlDebugTranslationService::s_key << "\t- "
426             //: Please preserve the line breaks and formatting
427             << tr("helps to see if a translated text\n"
428                "\t\t  will result in an elided text\n"
429                "\t\t  in QML elements.") << '\n'
430            << tr("Other services offered by qmltooling plugins that implement "
431                  "QQmlDebugServiceFactory and which can be found in the standard plugin "
432                  "paths will also be available and can be specified. If no \"services\" "
433                  "argument is given, all services found this way, including the default "
434                  "ones, are loaded.");
435         qWarning("%s", qPrintable(usage));
436     }
437 }
438 
receiveMessage()439 void QQmlDebugServerImpl::receiveMessage()
440 {
441     typedef QHash<QString, QQmlDebugService*>::const_iterator DebugServiceConstIt;
442 
443     // to be executed in debugger thread
444     Q_ASSERT(QThread::currentThread() == thread());
445 
446     if (!m_protocol)
447         return;
448 
449     QQmlDebugPacket in(m_protocol->read());
450 
451     QString name;
452 
453     in >> name;
454     if (name == QLatin1String("QDeclarativeDebugServer")) {
455         int op = -1;
456         in >> op;
457         if (op == 0) {
458             int version;
459             in >> version >> m_clientPlugins;
460 
461             //Get the supported QDataStream version
462             if (!in.atEnd()) {
463                 in >> s_dataStreamVersion;
464                 if (s_dataStreamVersion > QDataStream::Qt_DefaultCompiledVersion)
465                     s_dataStreamVersion = QDataStream::Qt_DefaultCompiledVersion;
466             }
467 
468             bool clientSupportsMultiPackets = false;
469             if (!in.atEnd())
470                 in >> clientSupportsMultiPackets;
471 
472             // Send the hello answer immediately, since it needs to arrive before
473             // the plugins below start sending messages.
474 
475             QQmlDebugPacket out;
476             QStringList pluginNames;
477             QList<float> pluginVersions;
478             if (clientSupportsMultiPackets) { // otherwise, disable all plugins
479                 const int count = m_plugins.count();
480                 pluginNames.reserve(count);
481                 pluginVersions.reserve(count);
482                 for (QHash<QString, QQmlDebugService *>::ConstIterator i = m_plugins.constBegin();
483                      i != m_plugins.constEnd(); ++i) {
484                     pluginNames << i.key();
485                     pluginVersions << i.value()->version();
486                 }
487             }
488 
489             out << QString(QStringLiteral("QDeclarativeDebugClient")) << 0 << protocolVersion
490                 << pluginNames << pluginVersions << dataStreamVersion();
491 
492             m_protocol->send(out.data());
493             m_connection->flush();
494 
495             QMutexLocker helloLock(&m_helloMutex);
496             m_gotHello = true;
497 
498             for (DebugServiceConstIt iter = m_plugins.constBegin(), cend = m_plugins.constEnd(); iter != cend; ++iter) {
499                 QQmlDebugService::State newState = QQmlDebugService::Unavailable;
500                 if (m_clientPlugins.contains(iter.key()))
501                     newState = QQmlDebugService::Enabled;
502                 m_changeServiceStateCalls.ref();
503                 changeServiceState(iter.key(), newState);
504             }
505 
506             m_helloCondition.wakeAll();
507 
508         } else if (op == 1) {
509             // Service Discovery
510             QStringList oldClientPlugins = m_clientPlugins;
511             in >> m_clientPlugins;
512 
513             for (DebugServiceConstIt iter = m_plugins.constBegin(), cend = m_plugins.constEnd(); iter != cend; ++iter) {
514                 const QString &pluginName = iter.key();
515                 QQmlDebugService::State newState = QQmlDebugService::Unavailable;
516                 if (m_clientPlugins.contains(pluginName))
517                     newState = QQmlDebugService::Enabled;
518 
519                 if (oldClientPlugins.contains(pluginName)
520                         != m_clientPlugins.contains(pluginName)) {
521                     m_changeServiceStateCalls.ref();
522                     changeServiceState(iter.key(), newState);
523                 }
524             }
525 
526         } else {
527             qWarning("QML Debugger: Invalid control message %d.", op);
528             protocolError();
529             return;
530         }
531 
532     } else {
533         if (m_gotHello) {
534             QHash<QString, QQmlDebugService *>::Iterator iter = m_plugins.find(name);
535             if (iter == m_plugins.end()) {
536                 qWarning() << "QML Debugger: Message received for missing plugin" << name << '.';
537             } else {
538                 QQmlDebugService *service = *iter;
539                 QByteArray message;
540                 while (!in.atEnd()) {
541                     in >> message;
542                     service->messageReceived(message);
543                 }
544             }
545         } else {
546             qWarning("QML Debugger: Invalid hello message.");
547         }
548 
549     }
550 }
551 
changeServiceState(const QString & serviceName,QQmlDebugService::State newState)552 void QQmlDebugServerImpl::changeServiceState(const QString &serviceName,
553                                              QQmlDebugService::State newState)
554 {
555     // to be executed in debugger thread
556     Q_ASSERT(QThread::currentThread() == thread());
557 
558     QQmlDebugService *service = m_plugins.value(serviceName);
559     if (service && service->state() != newState) {
560         service->stateAboutToBeChanged(newState);
561         service->setState(newState);
562         service->stateChanged(newState);
563     }
564 
565     m_changeServiceStateCalls.deref();
566 }
567 
removeThread()568 void QQmlDebugServerImpl::removeThread()
569 {
570     Q_ASSERT(m_thread.isFinished());
571     Q_ASSERT(QThread::currentThread() == thread());
572 
573     QThread *parentThread = m_thread.thread();
574 
575     delete m_connection;
576     m_connection = nullptr;
577 
578     // Move it back to the parent thread so that we can potentially restart it on a new thread.
579     moveToThread(parentThread);
580 }
581 
service(const QString & name) const582 QQmlDebugService *QQmlDebugServerImpl::service(const QString &name) const
583 {
584     return m_plugins.value(name);
585 }
586 
addEngine(QJSEngine * engine)587 void QQmlDebugServerImpl::addEngine(QJSEngine *engine)
588 {
589     // to be executed outside of debugger thread
590     Q_ASSERT(QThread::currentThread() != &m_thread);
591 
592     QMutexLocker locker(&m_helloMutex);
593     Q_ASSERT(!m_engineConditions.contains(engine));
594 
595     for (QQmlDebugService *service : qAsConst(m_plugins))
596         service->engineAboutToBeAdded(engine);
597 
598     m_engineConditions[engine].waitForServices(&m_helloMutex, m_plugins.count());
599 
600     for (QQmlDebugService *service : qAsConst(m_plugins))
601         service->engineAdded(engine);
602 }
603 
removeEngine(QJSEngine * engine)604 void QQmlDebugServerImpl::removeEngine(QJSEngine *engine)
605 {
606     // to be executed outside of debugger thread
607     Q_ASSERT(QThread::currentThread() != &m_thread);
608 
609     QMutexLocker locker(&m_helloMutex);
610     Q_ASSERT(m_engineConditions.contains(engine));
611 
612     for (QQmlDebugService *service : qAsConst(m_plugins))
613         service->engineAboutToBeRemoved(engine);
614 
615     m_engineConditions[engine].waitForServices(&m_helloMutex, m_plugins.count());
616 
617     for (QQmlDebugService *service : qAsConst(m_plugins))
618         service->engineRemoved(engine);
619 
620     m_engineConditions.remove(engine);
621 }
622 
hasEngine(QJSEngine * engine) const623 bool QQmlDebugServerImpl::hasEngine(QJSEngine *engine) const
624 {
625     QMutexLocker locker(&m_helloMutex);
626     QHash<QJSEngine *, EngineCondition>::ConstIterator i = m_engineConditions.constFind(engine);
627     // if we're still waiting the engine isn't fully "there", yet, nor fully removed.
628     return i != m_engineConditions.constEnd() && !i.value().isWaiting();
629 }
630 
addService(const QString & name,QQmlDebugService * service)631 bool QQmlDebugServerImpl::addService(const QString &name, QQmlDebugService *service)
632 {
633     // to be executed before thread starts
634     Q_ASSERT(!m_thread.isRunning());
635 
636     if (!service || m_plugins.contains(name))
637         return false;
638 
639     connect(service, &QQmlDebugService::messageToClient,
640             this, &QQmlDebugServerImpl::sendMessage);
641     connect(service, &QQmlDebugService::messagesToClient,
642             this, &QQmlDebugServerImpl::sendMessages);
643 
644     connect(service, &QQmlDebugService::attachedToEngine,
645             this, &QQmlDebugServerImpl::wakeEngine, Qt::QueuedConnection);
646     connect(service, &QQmlDebugService::detachedFromEngine,
647             this, &QQmlDebugServerImpl::wakeEngine, Qt::QueuedConnection);
648 
649     service->setState(QQmlDebugService::Unavailable);
650     m_plugins.insert(name, service);
651 
652     return true;
653 }
654 
removeService(const QString & name)655 bool QQmlDebugServerImpl::removeService(const QString &name)
656 {
657     // to be executed after thread ends
658     Q_ASSERT(!m_thread.isRunning());
659 
660     QQmlDebugService *service = m_plugins.value(name);
661     if (!service)
662         return false;
663 
664     m_plugins.remove(name);
665     service->setState(QQmlDebugService::NotConnected);
666 
667     disconnect(service, &QQmlDebugService::detachedFromEngine,
668                this, &QQmlDebugServerImpl::wakeEngine);
669     disconnect(service, &QQmlDebugService::attachedToEngine,
670                this, &QQmlDebugServerImpl::wakeEngine);
671 
672     disconnect(service, &QQmlDebugService::messagesToClient,
673                this, &QQmlDebugServerImpl::sendMessages);
674     disconnect(service, &QQmlDebugService::messageToClient,
675                this, &QQmlDebugServerImpl::sendMessage);
676 
677     return true;
678 }
679 
canSendMessage(const QString & name)680 bool QQmlDebugServerImpl::canSendMessage(const QString &name)
681 {
682     // to be executed in debugger thread
683     Q_ASSERT(QThread::currentThread() == thread());
684     return m_connection && m_connection->isConnected() && m_protocol &&
685             m_clientPlugins.contains(name);
686 }
687 
doSendMessage(const QString & name,const QByteArray & message)688 void QQmlDebugServerImpl::doSendMessage(const QString &name, const QByteArray &message)
689 {
690     QQmlDebugPacket out;
691     out << name << message;
692     m_protocol->send(out.data());
693 }
694 
sendMessage(const QString & name,const QByteArray & message)695 void QQmlDebugServerImpl::sendMessage(const QString &name, const QByteArray &message)
696 {
697     if (canSendMessage(name)) {
698         doSendMessage(name, message);
699         m_connection->flush();
700     }
701 }
702 
sendMessages(const QString & name,const QList<QByteArray> & messages)703 void QQmlDebugServerImpl::sendMessages(const QString &name, const QList<QByteArray> &messages)
704 {
705     if (canSendMessage(name)) {
706         QQmlDebugPacket out;
707         out << name;
708         for (const QByteArray &message : messages)
709             out << message;
710         m_protocol->send(out.data());
711         m_connection->flush();
712     }
713 }
714 
wakeEngine(QJSEngine * engine)715 void QQmlDebugServerImpl::wakeEngine(QJSEngine *engine)
716 {
717     // to be executed in debugger thread
718     Q_ASSERT(QThread::currentThread() == thread());
719 
720     QMutexLocker locker(&m_helloMutex);
721     m_engineConditions[engine].wake();
722 }
723 
waitForServices(QMutex * locked,int num)724 bool QQmlDebugServerImpl::EngineCondition::waitForServices(QMutex *locked, int num)
725 {
726     Q_ASSERT_X(numServices == 0, Q_FUNC_INFO, "Request to wait again before previous wait finished");
727     numServices = num;
728     return numServices > 0 ? condition->wait(locked) : true;
729 }
730 
wake()731 void QQmlDebugServerImpl::EngineCondition::wake()
732 {
733     if (--numServices == 0)
734         condition->wakeAll();
735     Q_ASSERT_X(numServices >=0, Q_FUNC_INFO, "Woken more often than #services.");
736 }
737 
setDevice(QIODevice * socket)738 void QQmlDebugServerImpl::setDevice(QIODevice *socket)
739 {
740     m_protocol = new QPacketProtocol(socket, this);
741     QObject::connect(m_protocol, &QPacketProtocol::readyRead,
742                      this, &QQmlDebugServerImpl::receiveMessage);
743     QObject::connect(m_protocol, &QPacketProtocol::error,
744                      this, &QQmlDebugServerImpl::protocolError);
745 
746     if (blockingMode())
747         m_protocol->waitForReadyRead(-1);
748 }
749 
protocolError()750 void QQmlDebugServerImpl::protocolError()
751 {
752     qWarning("QML Debugger: A protocol error has occurred! Giving up ...");
753     m_connection->disconnect();
754     // protocol might still be processing packages at this point
755     m_protocol->deleteLater();
756     m_protocol = nullptr;
757 }
758 
create(const QString & key)759 QQmlDebugConnector *QQmlDebugServerFactory::create(const QString &key)
760 {
761     // Cannot parent it to this because it gets moved to another thread
762     return (key == QLatin1String("QQmlDebugServer") ? new QQmlDebugServerImpl : nullptr);
763 }
764 
765 QT_END_NAMESPACE
766 
767 #include "qqmldebugserver.moc"
768