Revision control
++ // Copyright (C) 2018 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 "qqmlpreviewservice.h"
++ #include "qqmlclassicpreviewhandler.h"
++ #include "qqmlinplacepreviewhandler.h"
++
++ #include <QtCore/qpointer.h>
++ #include <QtQml/qqmlengine.h>
++ #include <QtQml/qqmlcomponent.h>
++ #include <QtQuick/qquickwindow.h>
++ #include <QtQuick/qquickitem.h>
++ #include <QtGui/qguiapplication.h>
++
++ #include <private/qquickpixmap_p.h>
++ #include <private/qqmldebugconnector_p.h>
++ #include <private/qversionedpacket_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ inline bool convertQmlPreviewHotReload(const char *v)
++ {
++ // Either not set or explicitly "1" or "true";
++ return v == nullptr || qstrcmp(v, "1") == 0 || qstrcmp(v, "true") == 0;
++ }
++
++ static bool qmlPreviewHotReloadEnabled()
++ {
++ static const bool enabled =
++ qmlGetConfigOption<bool, convertQmlPreviewHotReload>("QMLPREVIEW_HOTRELOAD");
++ return enabled;
++ }
++
++ const QString QQmlPreviewServiceImpl::s_key = QStringLiteral("QmlPreview");
++ using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>;
++
++ QQmlPreviewServiceImpl::QQmlPreviewServiceImpl(QObject *parent) :
++ QQmlDebugService(s_key, 1.0f, parent),
++ m_handler(std::make_unique<QQmlClassicPreviewHandler>())
++ {
++ m_handler->connectToService(this);
++ }
++
++ QQmlPreviewServiceImpl::~QQmlPreviewServiceImpl()
++ {
++ }
++
++ void QQmlPreviewServiceImpl::switchToInPlaceHandler()
++ {
++ // Transfer engines from the old handler to the new one
++ const auto engineList = m_handler->engines();
++ m_handler = std::make_unique<QQmlInPlacePreviewHandler>();
++ m_handler->connectToService(this);
++ for (QQmlEngine *engine : engineList)
++ m_handler->addEngine(engine);
++
++ // Send confirmation back to the client
++ QQmlDebugPacket response;
++ const bool enableInPlaceUpdates = true;
++ response << static_cast<qint8>(Confirmation) << enableInPlaceUpdates;
++ emit messageToClient(name(), response.data());
++ }
++
++ void QQmlPreviewServiceImpl::messageReceived(const QByteArray &data)
++ {
++ QQmlDebugPacket packet(data);
++ qint8 command;
++
++ packet >> command;
++ switch (command) {
++ case File: {
++ QString path;
++ QByteArray contents;
++ packet >> path >> contents;
++
++ const QUrl url = path.startsWith(QLatin1Char(':'))
++ ? QUrl(QLatin1String("qrc") + path)
++ : QUrl::fromLocalFile(path);
++
++ // Only a re-serve of a URL we already saw is an actual edit that needs relinking. The
++ // first time any file is served (e.g. while the whole app is still starting up) isn't
++ // dropping anything; treating it as such piles up the entire type registry as "dropped"
++ // and causes a mass relink storm on the first Load.
++ if (m_servedUrls.contains(url))
++ emit drop(url);
++ else
++ m_servedUrls.insert(url);
++ emit file(path, contents);
++
++ // Remember the first .qml file as the current URL, so that a Load command
++ // without an explicit URL can fall back to it.
++ if (m_currentUrl.isEmpty() && path.endsWith(".qml"))
++ m_currentUrl = url;
++
++ // Never auto-load here. The client must send an explicit Load command
++ // to trigger loading. Auto-loading based on File responses is racy
++ // (the Configuration message may not have arrived yet) and creates
++ // duplicate objects when the host process has already loaded the scene.
++ break;
++ }
++ case Directory: {
++ QString path;
++ QStringList entries;
++ packet >> path >> entries;
++ emit directory(path, entries);
++ break;
++ }
++ case Load: {
++ QUrl url;
++ packet >> url;
++ if (url.isEmpty())
++ url = m_currentUrl;
++ else
++ m_currentUrl = url;
++ emit load(url);
++ break;
++ }
++ case Error: {
++ QString file;
++ packet >> file;
++ emit error(file);
++ break;
++ }
++ case Rerun:
++ emit rerun();
++ break;
++ case ClearCache:
++ emit clearCache();
++ break;
++ case Zoom: {
++ float factor;
++ packet >> factor;
++ emit zoom(static_cast<qreal>(factor));
++ break;
++ }
++ case AnimationSpeed: {
++ float factor;
++ packet >> factor;
++ emit animationSpeed(qreal(factor));
++ break;
++ }
++ case Configuration: {
++ if (qmlPreviewHotReloadEnabled()) {
++ bool enableInPlaceUpdates;
++ packet >> enableInPlaceUpdates;
++ if (enableInPlaceUpdates
++ && !qobject_cast<QQmlInPlacePreviewHandler *>(m_handler.get())) {
++ // Schedule this to the main thread where the handler lives
++ // We're on the debug server thread here.
++ QMetaObject::invokeMethod(this, &QQmlPreviewServiceImpl::switchToInPlaceHandler);
++ } else if (!enableInPlaceUpdates
++ && qobject_cast<QQmlInPlacePreviewHandler *>(m_handler.get())) {
++ forwardError(QLatin1String("Cannot disable in-place updates once enabled"));
++ }
++ break;
++ } else {
++ Q_FALLTHROUGH();
++ }
++ }
++ default:
++ forwardError(QString::fromLatin1("Invalid command: %1").arg(command));
++ break;
++ }
++ }
++
++ void QQmlPreviewServiceImpl::engineAboutToBeAdded(QJSEngine *engine)
++ {
++ if (QQmlEngine *qmlEngine = qobject_cast<QQmlEngine *>(engine))
++ m_handler->addEngine(qmlEngine);
++ emit attachedToEngine(engine);
++ }
++
++ void QQmlPreviewServiceImpl::engineAboutToBeRemoved(QJSEngine *engine)
++ {
++ if (QQmlEngine *qmlEngine = qobject_cast<QQmlEngine *>(engine))
++ m_handler->removeEngine(qmlEngine);
++ emit detachedFromEngine(engine);
++ }
++
++ void QQmlPreviewServiceImpl::stateChanged(QQmlDebugService::State state)
++ {
++ if (state == Enabled) {
++ m_loader.reset(new QQmlPreviewFileLoader(this));
++ connect(this, &QQmlPreviewServiceImpl::load,
++ m_loader.data(), &QQmlPreviewFileLoader::whitelist, Qt::DirectConnection);
++ QV4::ExecutionEngine::setPreviewing(true);
++ m_fileEngine.reset(new QQmlPreviewFileEngineHandler(m_loader.data()));
++ } else {
++ QV4::ExecutionEngine::setPreviewing(false);
++ m_fileEngine.reset();
++ m_loader.reset();
++ }
++ }
++
++ void QQmlPreviewServiceImpl::forwardRequest(const QString &file)
++ {
++ QQmlDebugPacket packet;
++ packet << static_cast<qint8>(Request) << file;
++ emit messageToClient(name(), packet.data());
++ }
++
++ void QQmlPreviewServiceImpl::forwardError(const QString &error)
++ {
++ QQmlDebugPacket packet;
++ packet << static_cast<qint8>(Error) << error;
++ emit messageToClient(name(), packet.data());
++ }
++
++ void QQmlPreviewServiceImpl::forwardFps(const QQmlPreviewHandler::FpsInfo &frames)
++ {
++ QQmlDebugPacket packet;
++ packet << static_cast<qint8>(Fps)
++ << frames.numSyncs << frames.minSync << frames.maxSync << frames.totalSync
++ << frames.numRenders << frames.minRender << frames.maxRender << frames.totalRender;
++ emit messageToClient(name(), packet.data());
++ }
++
++ void QQmlPreviewServiceImpl::forwardHotReloadFailure(const QString &reason)
++ {
++ QQmlDebugPacket packet;
++ packet << static_cast<qint8>(HotReloadFailure) << reason;
++ emit messageToClient(name(), packet.data());
++ }
++
++ QQuickItem *QQmlPreviewServiceImpl::currentRootItem()
++ {
++ return m_handler->currentRootItem();
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qqmlpreviewservice.cpp"