From bfa609e33650061ef656a0c998d6a070fe0128c6 Mon Sep 17 00:00:00 2001
From: Olivier Goffart <olivier.goffart@nokia.com>
Date: Thu, 22 Jul 2010 13:00:26 +0200
Subject: [PATCH] QML JS Debugger: Better way to stream watch data

QVariant does not work as they can contain custom type that corrypt the stream
---
 src/plugins/debugger/qml/qmlengine.cpp        | 111 ++++--------------
 src/plugins/debugger/qml/qmlengine.h          |   1 -
 src/tools/qml/qmlobserver/jsdebuggeragent.cpp |  88 +++++++++++++-
 3 files changed, 105 insertions(+), 95 deletions(-)

diff --git a/src/plugins/debugger/qml/qmlengine.cpp b/src/plugins/debugger/qml/qmlengine.cpp
index c00d308fa98..27163ef3fae 100644
--- a/src/plugins/debugger/qml/qmlengine.cpp
+++ b/src/plugins/debugger/qml/qmlengine.cpp
@@ -81,6 +81,19 @@
 namespace Debugger {
 namespace Internal {
 
+QDataStream& operator>>(QDataStream& s, WatchData &data)
+{
+    data = WatchData();
+    QString value;
+    QString type;
+    bool hasChildren;
+    s >> data.exp >> data.name >> value >> type >> hasChildren;
+    data.setType(type, false);
+    data.setValue(value);
+    data.setHasChildren(hasChildren);
+    return s;
+}
+
 class QmlResponse
 {
 public:
@@ -530,74 +543,6 @@ void QmlEngine::updateWatchData(const WatchData &data)
     }
 }
 
-void QmlEngine::updateSubItem(WatchData &data, const QVariant &value)
-{
-    QList<WatchData> children;
-    switch (value.userType()) {
-        case QVariant::Invalid:{
-            const QString nullValue = QLatin1String("<undefined>");
-            data.setType(nullValue, false);
-            data.setValue(nullValue);
-            break;}
-        case QVariant::Map: {
-            data.setType(QString::fromLatin1("Object"), false);
-            QVariantMap map = value.toMap();
-            for (QVariantMap::const_iterator it = map.constBegin(); it != map.constEnd(); ++it) {
-                WatchData childData;
-                childData.iname = data.iname + '.' + it.key().toLatin1();
-                childData.exp = it.key().toLatin1();
-                childData.name = it.key();
-                updateSubItem(childData, it.value());
-                children.append(childData);
-            }
-            break;
-        }
-        case QVariant::List: {
-            data.setType(QString::fromLatin1("Array"), false);
-            QVariantList list = value.toList();
-            QStringList values;
-            for (int i = 0; i < list.count(); ++i) {
-                WatchData childData;
-                childData.exp = QByteArray::number(i);
-                childData.iname = data.iname + '.' + childData.exp;
-                childData.name = QString::number(i);
-                updateSubItem(childData, list.at(i));
-                children.append(childData);
-                values.append(list.at(i).toString());
-            }
-            data.setValue(QLatin1Char('[') + values.join(QLatin1String(",")) + QLatin1Char(']'));
-            break;
-        }
-        case QVariant::Bool:
-            data.setType(QLatin1String("Bool"), false);
-            data.setValue(value.toBool() ? QLatin1String("true") : QLatin1String("false"));
-            data.setHasChildren(false);
-            break;
-        case QVariant::Date:
-        case QVariant::DateTime:
-        case QVariant::Time:
-            data.setType(QLatin1String("Date"), false);
-            data.setValue(value.toDateTime().toString());
-            data.setHasChildren(false);
-            break;
-        case QVariant::UInt:
-        case QVariant::Int:
-        case QVariant::Double:
-            data.setType(QLatin1String("Number"), false);
-            data.setValue(QString::number(value.toDouble()));
-            break;
-        case QVariant::String:
-            data.setType(QLatin1String("String"), false);
-            data.setValue(value.toString());
-            break;
-        default:
-            data.setType(QString::fromLatin1(value.typeName()), false);
-            data.setValue(value.toString());
-    }
-    data.setHasChildren(!children.isEmpty());
-    watchHandler()->insertData(data);
-}
-
 DebuggerEngine *createQmlEngine(const DebuggerStartParameters &sp)
 {
     return new QmlEngine(sp);
@@ -632,8 +577,8 @@ void QmlEngine::messageReceived(const QByteArray &message)
         notifyInferiorSpontaneousStop();
 
         QList<QPair<QString, QPair<QString, qint32> > > backtrace;
-        QList<QPair<QString, QVariant> > watches;
-        QVariant locals;
+        QList<WatchData> watches;
+        QList<WatchData> locals;
         stream >> backtrace >> watches >> locals;
 
         StackFrames stackFrames;
@@ -651,33 +596,23 @@ void QmlEngine::messageReceived(const QByteArray &message)
 
         watchHandler()->beginCycle();
 
-        typedef QPair<QString, QVariant > Iterator2;
-        foreach (const Iterator2 &it, watches) {
-            WatchData data;
-            data.name = it.first;
-            data.exp = it.first.toUtf8();
+        foreach (WatchData data, watches) {
             data.iname = watchHandler()->watcherName(data.exp);
-            updateSubItem(data, it.second);
+            watchHandler()->insertData(data);
         }
 
-//        QVariantMap localsMap = locals.toMap();
-//        for (QVariantMap::const_iterator it = localsMap.constBegin(); it != localsMap.constEnd(); it++)
-        {
-            WatchData localData;
-            localData.iname = "local";
-            localData.name = QLatin1String("local");
-            updateSubItem(localData, locals);
+        foreach (WatchData data, locals) {
+            data.iname = "local." + data.exp;
+            watchHandler()->insertData(data);
         }
 
         watchHandler()->endCycle();
 
     } else if (command == "RESULT") {
         WatchData data;
-        QVariant variant;
-        stream >> data.iname >> data.name >> variant;
-        data.exp = data.name.toUtf8();
-        updateSubItem(data, variant);
-        qDebug() << Q_FUNC_INFO << this << data.name << data.iname << variant;
+        QByteArray iname;
+        stream >> iname >> data;
+        data.iname = iname;
         watchHandler()->insertData(data);
     } else {
         qDebug() << Q_FUNC_INFO << "Unknown command: " << command;
diff --git a/src/plugins/debugger/qml/qmlengine.h b/src/plugins/debugger/qml/qmlengine.h
index ba6213156a4..dc4d9010af7 100644
--- a/src/plugins/debugger/qml/qmlengine.h
+++ b/src/plugins/debugger/qml/qmlengine.h
@@ -120,7 +120,6 @@ private:
     void maybeBreakNow(bool byFunction);
     void updateWatchData(const WatchData &data);
     void updateLocals();
-    void updateSubItem(WatchData& data, const QVariant& value);
 
     unsigned int debuggerCapabilities() const;
 
diff --git a/src/tools/qml/qmlobserver/jsdebuggeragent.cpp b/src/tools/qml/qmlobserver/jsdebuggeragent.cpp
index c54c721f7b8..d79b18c4d4e 100644
--- a/src/tools/qml/qmlobserver/jsdebuggeragent.cpp
+++ b/src/tools/qml/qmlobserver/jsdebuggeragent.cpp
@@ -47,9 +47,78 @@
 #include <QtScript/QScriptContextInfo>
 #include <QtCore/QDebug>
 #include <QtCore/QUrl>
+#include <QtCore/QDateTime>
+#include <QtScript/qscriptvalueiterator.h>
 
 QT_BEGIN_NAMESPACE
 
+namespace {
+struct JSAgentWatchData {
+    QByteArray exp;
+    QString name;
+    QString value;
+    QString type;
+    bool hasChildren;
+
+    static JSAgentWatchData fromScriptValue(const QString &expression, const QScriptValue &value)
+    {
+        JSAgentWatchData data;
+        data.exp = expression.toUtf8();
+        data.name = expression;
+        data.hasChildren = false;
+        data.value = value.toString();
+        if (value.isArray()) {
+            data.type = QLatin1String("Array");
+            data.value = QString::fromLatin1("[Array of length %1]").arg(value.property("length").toString());
+            data.hasChildren = true;
+        } else if (value.isBool()) {
+            data.type = QLatin1String("Bool");
+//            data.value = value.toBool() ? QLatin1String("true") : QLatin1String("false");
+        } else if (value.isDate()) {
+            data.type = QLatin1String("Date");
+            data.value = value.toDateTime().toString();
+        } else if (value.isError()) {
+            data.type = QLatin1String("Error");
+        } else if (value.isFunction()) {
+            data.type = QLatin1String("Function");
+        } else if (value.isUndefined()) {
+            data.type = QLatin1String("<undefined>");
+        } else if (value.isNumber()) {
+            data.type = QLatin1String("Number");
+        } else if (value.isRegExp()) {
+            data.type = QLatin1String("RegExp");
+        } else if (value.isString()) {
+            data.type = QLatin1String("String");
+        } else if (value.isVariant()) {
+            data.type = QLatin1String("Variant");
+        } else if (value.isObject()) {
+            data.type = QLatin1String("Object");
+            data.hasChildren = true;
+            data.value = QLatin1String("[Object]");
+/*        } else if (value.isQMetaObject()) {
+            data.setType(QLatin1String("QMetaObject"), false);
+            data.setValue(QString(QLatin1Char(' ')));
+        } else if (value.isQObject()) {
+            data.type = QLatin1String("QObject");
+            data.hasChildren = true;*/
+        } else if (value.isNull()) {
+            data.type = QLatin1String("<null>");
+        } else {
+            data.type = QLatin1String("<unknown>");
+        }
+        return data;
+    }
+};
+
+
+QDataStream& operator<<(QDataStream& s, const JSAgentWatchData& data)
+{
+    return s << data.exp << data.name << data.value << data.type << data.hasChildren;
+}
+
+}
+
+
 /*!
   Constructs a new agent for the given \a engine. The agent will
   report debugging-related events (e.g. step completion) to the given
@@ -219,13 +288,13 @@ void JSDebuggerAgent::messageReceived(const QByteArray& message)
         QString expr;
         ds >> id >> expr;
 
-        QVariant val = engine()->evaluate(expr).toVariant();
+        JSAgentWatchData data = JSAgentWatchData::fromScriptValue(expr, engine()->evaluate(expr));
         // Clear any exceptions occurred during locals evaluation.
         engine()->clearExceptions();
 
         QByteArray reply;
         QDataStream rs(&reply, QIODevice::WriteOnly);
-        rs << QByteArray("RESULT") << id << expr << val;
+        rs << QByteArray("RESULT") << id << data;
         sendMessage(reply);
         state = oldState;
 
@@ -265,18 +334,25 @@ void JSDebuggerAgent::stopped()
         }
         backtrace.append(qMakePair(functionName, qMakePair( QUrl(info.fileName()).toLocalFile(), info.lineNumber() ) ) );
     }
-    QList<QPair<QString, QVariant> > watches;
+    QList<JSAgentWatchData> watches;
     foreach (const QString &expr, watchExpressions) {
-        watches << qMakePair(expr,  engine()->evaluate(expr).toVariant());
+        watches << JSAgentWatchData::fromScriptValue(expr,  engine()->evaluate(expr));
+    }
+
+    QList<JSAgentWatchData> locals;
+    QScriptValue activationObject = engine()->currentContext()->activationObject();
+    QScriptValueIterator it(activationObject);
+    while (it.hasNext()) {
+        it.next();
+        locals << JSAgentWatchData::fromScriptValue(it.name(), it.value());
     }
 
     // Clear any exceptions occurred during locals evaluation.
     engine()->clearExceptions();
 
-
     QByteArray reply;
     QDataStream rs(&reply, QIODevice::WriteOnly);
-    rs << QByteArray("STOPPED") << backtrace << watches << engine()->currentContext()->activationObject().toVariant();
+    rs << QByteArray("STOPPED") << backtrace << watches << locals;
     sendMessage(reply);
 
     loop.exec(QEventLoop::ExcludeUserInputEvents);
-- 
GitLab