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 "globalinspector.h"
++ #include "highlight.h"
++ #include "inspecttool.h"
++
++ #include <private/qqmldebugserviceinterfaces_p.h>
++ #include <private/qabstractanimation_p.h>
++ #include <private/qqmlcomponent_p.h>
++ #include <private/qqmldebugconnector_p.h>
++ #include <private/qversionedpacket_p.h>
++
++ #include <QtGui/qwindow.h>
++ #if QT_CONFIG(regularexpression)
++ # include <QtCore/qregularexpression.h>
++ #endif
++ //INSPECTOR SERVICE PROTOCOL
++ // <HEADER><COMMAND><DATA>
++ // <HEADER> : <type{request, response, event}><requestId/eventId>[<response_success_bool>]
++ // <COMMAND> : {"enable", "disable", "select", "setAnimationSpeed",
++ // "showAppOnTop", "createObject", "destroyObject", "moveObject"}
++ // <DATA> : select: <debugIds_int_list>
++ // setAnimationSpeed: <speed_real>
++ // showAppOnTop: <set_bool>
++ // createObject: <qml_string><parentId_int><imports_string_list><filename_string>
++ // destroyObject: <debugId_int>
++ // moveObject: <debugId_int><newParentId_int>
++ // Response for "destroyObject" carries the <debugId_int> of the destroyed object.
++
++ QT_BEGIN_NAMESPACE
++
++ using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>;
++
++ const char REQUEST[] = "request";
++ const char RESPONSE[] = "response";
++ const char EVENT[] = "event";
++ const char ENABLE[] = "enable";
++ const char DISABLE[] = "disable";
++ const char SELECT[] = "select";
++ const char SET_ANIMATION_SPEED[] = "setAnimationSpeed";
++ const char SHOW_APP_ON_TOP[] = "showAppOnTop";
++ const char CREATE_OBJECT[] = "createObject";
++ const char DESTROY_OBJECT[] = "destroyObject";
++ const char MOVE_OBJECT[] = "moveObject";
++
++ namespace QmlJSDebugger {
++
++ void GlobalInspector::removeFromSelectedItems(QObject *object)
++ {
++ if (QQuickItem *item = qobject_cast<QQuickItem*>(object)) {
++ if (m_selectedItems.removeOne(item))
++ delete m_highlightItems.take(item);
++ }
++ }
++
++ void GlobalInspector::setSelectedItems(const QList<QQuickItem *> &items)
++ {
++ if (!syncSelectedItems(items))
++ return;
++
++ QList<QObject*> objectList;
++ objectList.reserve(items.size());
++ for (QQuickItem *item : items)
++ objectList << item;
++
++ sendCurrentObjects(objectList);
++ }
++
++ void GlobalInspector::showSelectedItemName(QQuickItem *item, const QPointF &point)
++ {
++ SelectionHighlight *highlightItem = m_highlightItems.value(item, 0);
++ if (highlightItem)
++ highlightItem->showName(point);
++ }
++
++ void GlobalInspector::sendCurrentObjects(const QList<QObject*> &objects)
++ {
++ QQmlDebugPacket ds;
++
++ ds << QByteArray(EVENT) << m_eventId++ << QByteArray(SELECT);
++
++ QList<int> debugIds;
++ debugIds.reserve(objects.size());
++ for (QObject *object : objects)
++ debugIds << QQmlDebugService::idForObject(object);
++ ds << debugIds;
++
++ emit messageToClient(QQmlInspectorService::s_key, ds.data());
++ }
++
++ static bool reparentQmlObject(QObject *object, QObject *newParent)
++ {
++ if (!newParent)
++ return false;
++
++ object->setParent(newParent);
++ QQuickItem *newParentItem = qobject_cast<QQuickItem*>(newParent);
++ QQuickItem *item = qobject_cast<QQuickItem*>(object);
++ if (newParentItem && item)
++ item->setParentItem(newParentItem);
++ return true;
++ }
++
++ class ObjectCreator : public QObject
++ {
++ Q_OBJECT
++ public:
++ ObjectCreator(int requestId, QQmlEngine *engine, QObject *parent) :
++ QObject(parent), m_component(engine), m_requestId(requestId)
++ {
++ connect(&m_component, &QQmlComponent::statusChanged, this, &ObjectCreator::tryCreateObject);
++ }
++
++ void run(const QByteArray &qml, const QUrl &filename)
++ {
++ m_component.setData(qml, filename);
++ }
++
++ void tryCreateObject(QQmlComponent::Status status)
++ {
++ switch (status) {
++ case QQmlComponent::Error:
++ emit result(m_requestId, false);
++ delete this;
++ return;
++ case QQmlComponent::Ready: {
++ // Stuff might have changed. We have to lookup the parentContext again.
++ QQmlContext *parentContext = QQmlEngine::contextForObject(parent());
++ if (!parentContext) {
++ emit result(m_requestId, false);
++ } else {
++ QObject *newObject = m_component.create(parentContext);
++ if (newObject && reparentQmlObject(newObject, parent()))
++ emit result(m_requestId, true);
++ else
++ emit result(m_requestId, false);
++ }
++ deleteLater(); // The component might send more signals
++ return;
++ }
++ default:
++ break;
++ }
++ }
++
++ signals:
++ void result(int requestId, bool success);
++
++ private:
++ QQmlComponent m_component;
++ int m_requestId;
++ };
++
++ bool GlobalInspector::createQmlObject(int requestId, const QString &qml, QObject *parent,
++ const QStringList &importList, const QString &filename)
++ {
++ if (!parent)
++ return false;
++
++ QQmlContext *parentContext = QQmlEngine::contextForObject(parent);
++ if (!parentContext)
++ return false;
++
++ QString imports;
++ for (const QString &s : importList)
++ imports += s + QLatin1Char('\n');
++
++ ObjectCreator *objectCreator = new ObjectCreator(requestId, parentContext->engine(), parent);
++ connect(objectCreator, &ObjectCreator::result, this, &GlobalInspector::sendResult);
++ objectCreator->run((imports + qml).toUtf8(), QUrl::fromLocalFile(filename));
++ return true;
++ }
++
++ void GlobalInspector::addWindow(QQuickWindow *window)
++ {
++ m_windowInspectors.append(new QQuickWindowInspector(window, this));
++ }
++
++ void GlobalInspector::removeWindow(QQuickWindow *window)
++ {
++ for (QList<QmlJSDebugger::QQuickWindowInspector *>::Iterator i = m_windowInspectors.begin();
++ i != m_windowInspectors.end();) {
++ if ((*i)->quickWindow() == window) {
++ delete *i;
++ i = m_windowInspectors.erase(i);
++ } else {
++ ++i;
++ }
++ }
++ }
++
++ void GlobalInspector::setParentWindow(QQuickWindow *window, QWindow *parentWindow)
++ {
++ for (QmlJSDebugger::QQuickWindowInspector *inspector : std::as_const(m_windowInspectors)) {
++ if (inspector->quickWindow() == window)
++ inspector->setParentWindow(parentWindow);
++ }
++ }
++
++ bool GlobalInspector::syncSelectedItems(const QList<QQuickItem *> &items)
++ {
++ bool selectionChanged = false;
++
++ // Disconnect and remove items that are no longer selected
++ const auto selectedItemsCopy = m_selectedItems;
++ for (QQuickItem *item : selectedItemsCopy) {
++ if (items.contains(item))
++ continue;
++
++ selectionChanged = true;
++ item->disconnect(this);
++ m_selectedItems.removeOne(item);
++ delete m_highlightItems.take(item);
++ }
++
++ // Connect and add newly selected items
++ for (QQuickItem *item : items) {
++ if (m_selectedItems.contains(item))
++ continue;
++
++ selectionChanged = true;
++ connect(item, &QObject::destroyed, this, &GlobalInspector::removeFromSelectedItems);
++ m_selectedItems.append(item);
++ for (QQuickWindowInspector *inspector : std::as_const(m_windowInspectors)) {
++ if (inspector->isEnabled() && inspector->quickWindow() == item->window()) {
++ m_highlightItems.insert(item, new SelectionHighlight(titleForItem(item), item,
++ inspector->overlay()));
++ break;
++ }
++ }
++ }
++
++ return selectionChanged;
++ }
++
++ QString GlobalInspector::titleForItem(QQuickItem *item) const
++ {
++ QString className = QLatin1String(item->metaObject()->className());
++ QString objectStringId = idStringForObject(item);
++
++ #if QT_CONFIG(regularexpression)
++ className.remove(QRegularExpression(QLatin1String("_QMLTYPE_\\d+")));
++ className.remove(QRegularExpression(QLatin1String("_QML_\\d+")));
++ #endif
++ if (className.startsWith(QLatin1String("QQuick")))
++ className = className.mid(6);
++
++ QString constructedName;
++
++ if (!objectStringId.isEmpty()) {
++ constructedName = objectStringId + QLatin1String(" (") + className + QLatin1Char(')');
++ } else if (!item->objectName().isEmpty()) {
++ constructedName = item->objectName() + QLatin1String(" (") + className + QLatin1Char(')');
++ } else {
++ constructedName = className;
++ }
++
++ return constructedName;
++ }
++
++ QString GlobalInspector::idStringForObject(QObject *obj) const
++ {
++ QQmlContext *context = qmlContext(obj);
++ if (context) {
++ QQmlRefPointer<QQmlContextData> cdata = QQmlContextData::get(context);
++ if (cdata)
++ return cdata->findObjectId(obj);
++ }
++ return QString();
++ }
++
++ void GlobalInspector::processMessage(const QByteArray &message)
++ {
++ bool success = true;
++ QQmlDebugPacket ds(message);
++
++ QByteArray type;
++ ds >> type;
++
++ int requestId = -1;
++ if (type == REQUEST) {
++ QByteArray command;
++ ds >> requestId >> command;
++
++ if (command == ENABLE) {
++ for (QQuickWindowInspector *inspector : std::as_const(m_windowInspectors))
++ inspector->setEnabled(true);
++ success = !m_windowInspectors.isEmpty();
++ } else if (command == DISABLE) {
++ setSelectedItems(QList<QQuickItem*>());
++ for (QQuickWindowInspector *inspector : std::as_const(m_windowInspectors))
++ inspector->setEnabled(false);
++ success = !m_windowInspectors.isEmpty();
++ } else if (command == SELECT) {
++ QList<int> debugIds;
++ ds >> debugIds;
++
++ QList<QQuickItem *> selectedObjects;
++ for (int debugId : std::as_const(debugIds)) {
++ if (QQuickItem *obj =
++ qobject_cast<QQuickItem *>(QQmlDebugService::objectForId(debugId)))
++ selectedObjects << obj;
++ }
++ syncSelectedItems(selectedObjects);
++ } else if (command == SET_ANIMATION_SPEED) {
++ qreal speed;
++ ds >> speed;
++ QUnifiedTimer::instance()->setSpeedModifier(1 / speed);
++ } else if (command == SHOW_APP_ON_TOP) {
++ bool showOnTop;
++ ds >> showOnTop;
++ for (QmlJSDebugger::QQuickWindowInspector *inspector : std::as_const(m_windowInspectors))
++ inspector->setShowAppOnTop(showOnTop);
++ success = !m_windowInspectors.isEmpty();
++ } else if (command == CREATE_OBJECT) {
++ QString qml;
++ int parentId;
++ QString filename;
++ QStringList imports;
++ ds >> qml >> parentId >> imports >> filename;
++ if (QObject *parent = QQmlDebugService::objectForId(parentId)) {
++ if (createQmlObject(requestId, qml, parent, imports, filename))
++ return; // will callback for result
++ else {
++ success = false;
++ }
++ } else {
++ success = false;
++ }
++
++ } else if (command == DESTROY_OBJECT) {
++ int debugId;
++ ds >> debugId;
++ if (QObject *obj = QQmlDebugService::objectForId(debugId))
++ delete obj;
++ else
++ success = false;
++
++ } else if (command == MOVE_OBJECT) {
++ int debugId, newParent;
++ ds >> debugId >> newParent;
++ success = reparentQmlObject(QQmlDebugService::objectForId(debugId),
++ QQmlDebugService::objectForId(newParent));
++ } else {
++ qWarning() << "Warning: Not handling command:" << command;
++ success = false;
++ }
++ } else {
++ qWarning() << "Warning: Not handling type:" << type << REQUEST;
++ success = false;
++ }
++
++ sendResult(requestId, success);
++ }
++
++ void GlobalInspector::sendResult(int requestId, bool success)
++ {
++ QQmlDebugPacket rs;
++ rs << QByteArray(RESPONSE) << requestId << success;
++ emit messageToClient(QQmlInspectorService::s_key, rs.data());
++ }
++
++ GlobalInspector::~GlobalInspector()
++ {
++ // Everything else is parented
++ qDeleteAll(m_highlightItems);
++ }
++
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_globalinspector.cpp"
++
++ #include <globalinspector.moc>