Remove "All rights reserved" line from license headers.
[profile/ivi/qtdeclarative.git] / src / declarative / debugger / qv8debugservice.cpp
index 7729e5b..bcc9296 100644 (file)
@@ -1,8 +1,7 @@
 /****************************************************************************
 **
-** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
-** Contact: Nokia Corporation (qt-info@nokia.com)
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
 **
 ** This file is part of the QtDeclarative module of the Qt Toolkit.
 **
 **
 **
 **
+**
 ** $QT_END_LICENSE$
 **
 ****************************************************************************/
 
 #include "qv8debugservice_p.h"
 #include "qdeclarativedebugservice_p_p.h"
-#include "qv8debug_p.h"
-#include "qv8engine_p.h"
-#include "qdeclarativeengine_p.h"
+#include <private/qjsconverter_impl_p.h>
+#include <private/qv8engine_p.h>
 
-#include <QtCore/QEventLoop>
 #include <QtCore/QHash>
 #include <QtCore/QFileInfo>
+#include <QtCore/QMutex>
+
+//V8 DEBUG SERVICE PROTOCOL
+// <HEADER><COMMAND><DATA>
+// <HEADER> : "V8DEBUG"
+// <COMMAND> : ["connect", "disconnect", "interrupt",
+//              "v8request", "v8message", "breakonsignal",
+//              "breakaftercompile"]
+// <DATA> : connect, disconnect, interrupt: empty
+//          v8request, v8message: <JSONrequest_string>
+//          breakonsignal: <signalname_string><enabled_bool>
+//          breakaftercompile: <enabled_bool>
+
+const char *V8_DEBUGGER_KEY_VERSION = "version";
+const char *V8_DEBUGGER_KEY_CONNECT = "connect";
+const char *V8_DEBUGGER_KEY_INTERRUPT = "interrupt";
+const char *V8_DEBUGGER_KEY_DISCONNECT = "disconnect";
+const char *V8_DEBUGGER_KEY_REQUEST = "v8request";
+const char *V8_DEBUGGER_KEY_V8MESSAGE = "v8message";
+const char *V8_DEBUGGER_KEY_BREAK_ON_SIGNAL = "breakonsignal";
+const char *V8_DEBUGGER_KEY_BREAK_AFTER_COMPILE = "breakaftercompile";
 
 QT_BEGIN_NAMESPACE
 
+struct SignalHandlerData
+{
+    QString functionName;
+    bool enabled;
+};
+
 Q_GLOBAL_STATIC(QV8DebugService, v8ServiceInstance)
 
+// DebugMessageHandler will call back already when the QV8DebugService constructor is
+// running, we therefore need a plain pointer.
+static QV8DebugService *v8ServiceInstancePtr = 0;
+
+void DebugMessageDispatchHandler()
+{
+    QMetaObject::invokeMethod(v8ServiceInstancePtr, "processDebugMessages", Qt::QueuedConnection);
+}
+
 void DebugMessageHandler(const v8::Debug::Message& message)
 {
     v8::DebugEvent event = message.GetEvent();
 
     if (event != v8::Break && event != v8::Exception &&
-            event != v8::AfterCompile && event != v8::BeforeCompile) {
-        return;
-    }
-
-    QByteArray response(QV8Engine::toStringStatic(message.GetJSON()).toUtf8());
-
-    QV8DebugService *service = QV8DebugService::instance();
-    service->debugMessageHandler(response);
-
-    if ((event == v8::Break || event == v8::Exception) &&
-            !message.WillStartRunning()) {
-        service->executionStopped();
-    } else if (event == v8::AfterCompile) {
-        service->appendSourcePath(response);
-    } //TODO::v8::Exception
+            event != v8::AfterCompile && event != v8::BeforeCompile)
+            return;
+    v8ServiceInstancePtr->debugMessageHandler(QJSConverter::toString(message.GetJSON()), event);
 }
 
 class QV8DebugServicePrivate : public QDeclarativeDebugServicePrivate
 {
 public:
     QV8DebugServicePrivate()
-        :initialized(false)
+        : connectReceived(false)
+        , breakAfterCompile(false)
+        , engine(0)
     {
-        //Create a new isolate
-        isolate = v8::Isolate::New();
-
-        //Enter the isolate and initialize
-        v8::Isolate::Scope i_scope(isolate);
-        v8::V8::Initialize();
-
-        //Create an instance in the new isolate
-        engine = new QJSEngine();
     }
 
-    ~QV8DebugServicePrivate()
-    {
-        delete engine;
-        isolate->Dispose();
-    }
+    void initializeDebuggerThread();
 
-    bool initialized;
-    QJSEngine *engine;
-    v8::Isolate *isolate;
-    QList<QDeclarativeEngine *> engines;
-    QEventLoop loop;
-    QHash<QString,QString> sourcePath;
-    QHash<QString,QByteArray> requestCache;
+    static QByteArray packMessage(const QString &type, const QString &message = QString());
+
+    bool connectReceived;
+    bool breakAfterCompile;
+    QMutex initializeMutex;
+    QStringList breakOnSignals;
+    const QV8Engine *engine;
 };
 
 QV8DebugService::QV8DebugService(QObject *parent)
-    : QDeclarativeDebugService(*(new QV8DebugServicePrivate()), QLatin1String("V8Debugger"), parent)
+    : QDeclarativeDebugService(*(new QV8DebugServicePrivate()),
+                               QLatin1String("V8Debugger"), 2, parent)
 {
     Q_D(QV8DebugService);
-    v8::Debug::SetMessageHandler2(DebugMessageHandler);
-    if (status() == Enabled) {
+    v8ServiceInstancePtr = this;
+    // wait for statusChanged() -> initialize()
+    d->initializeMutex.lock();
+    if (registerService() == Enabled) {
+        init();
         // ,block mode, client attached
-        while (!d->initialized) {
+        while (!d->connectReceived) {
             waitForMessage();
         }
+    } else {
+        d->initializeMutex.unlock();
     }
 }
 
@@ -129,148 +146,148 @@ QV8DebugService *QV8DebugService::instance()
     return v8ServiceInstance();
 }
 
-void QV8DebugService::addEngine(QDeclarativeEngine *engine)
+void QV8DebugService::initialize(const QV8Engine *engine)
 {
-    Q_D(QV8DebugService);
-    Q_ASSERT(engine);
-    Q_ASSERT(!d->engines.contains(engine));
-
-    d->engines.append(engine);
+    // just make sure that the service is properly registered
+    v8ServiceInstance()->setEngine(engine);
 }
 
-void QV8DebugService::removeEngine(QDeclarativeEngine *engine)
+void QV8DebugService::setEngine(const QV8Engine *engine)
 {
     Q_D(QV8DebugService);
-    Q_ASSERT(engine);
-    Q_ASSERT(d->engines.contains(engine));
 
-    d->engines.removeAll(engine);
+    d->engine = engine;
 }
 
-void QV8DebugService::debugMessageHandler(QByteArray message)
+void QV8DebugService::debugMessageHandler(const QString &message, const v8::DebugEvent &event)
 {
-    sendMessage(packMessage(message));
+    Q_D(QV8DebugService);
+    sendMessage(QV8DebugServicePrivate::packMessage(QLatin1String(V8_DEBUGGER_KEY_V8MESSAGE), message));
+    if (event == v8::AfterCompile && d->breakAfterCompile)
+        scheduledDebugBreak(true);
 }
 
-void QV8DebugService::executionStopped()
+void QV8DebugService::signalEmitted(const QString &signal)
 {
+    //This function is only called by QDeclarativeBoundSignal
+    //only if there is a slot connected to the signal. Hence, there
+    //is no need for additional check.
     Q_D(QV8DebugService);
 
-    if (!d->loop.isRunning()) {
-        d->loop.exec(QEventLoop::ExcludeUserInputEvents);
+    //Parse just the name and remove the class info
+    //Normalize to Lower case.
+    QString signalName = signal.left(signal.indexOf(QLatin1String("("))).toLower();
+
+    foreach (const QString &signal, d->breakOnSignals) {
+        if (signal == signalName) {
+            scheduledDebugBreak(true);
+            break;
+        }
     }
 }
 
-void QV8DebugService::appendSourcePath(QByteArray message)
+// executed in the gui thread
+void QV8DebugService::init()
 {
     Q_D(QV8DebugService);
+    v8::Debug::SetMessageHandler2(DebugMessageHandler);
+    v8::Debug::SetDebugMessageDispatchHandler(DebugMessageDispatchHandler);
+    d->initializeMutex.unlock();
+}
 
-    QVariantMap msgMap;
-    /* Parse the byte string in a separate isolate
-    This will ensure that the debug message handler does not
-    receive any messages related to this operation */
-    {
-        v8::Isolate::Scope i_scope(d->isolate);
-        QString req(message);
-        QJSValue parser = d->engine->evaluate(QLatin1String("JSON.parse"));
-        QJSValue out = parser.call(QJSValue(), QJSValueList() << QJSValue(req));
-        msgMap = out.toVariant().toMap();
-    }
-
-    QString sourcePath(msgMap.value(QLatin1String("body")).toMap().value(QLatin1String("script")).toMap().value(QLatin1String("name")).toString());
-    QString fileName(QFileInfo(sourcePath).fileName());
-
-    d->sourcePath.insert(fileName, sourcePath);
+// executed in the gui thread
+void QV8DebugService::scheduledDebugBreak(bool schedule)
+{
+    if (schedule)
+        v8::Debug::DebugBreak();
+    else
+        v8::Debug::CancelDebugBreak();
+}
 
-    //Check if there are any pending breakpoint requests for this file
-    if (d->requestCache.contains(fileName)) {
-        QList<QByteArray> list = d->requestCache.values(fileName);
-        d->requestCache.remove(fileName);
-        foreach (QByteArray request, list) {
-            request.replace(fileName.toUtf8(), sourcePath.toUtf8());
-            sendDebugMessage(request);
-        }
+// executed in the debugger thread
+void QV8DebugService::statusChanged(QDeclarativeDebugService::Status newStatus)
+{
+    Q_D(QV8DebugService);
+    if (newStatus == Enabled) {
+        // execute in GUI thread
+        d->initializeMutex.lock();
+        QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection);
     }
 }
 
+// executed in the debugger thread
 void QV8DebugService::messageReceived(const QByteArray &message)
 {
     Q_D(QV8DebugService);
 
     QDataStream ds(message);
-    QByteArray command;
-    ds >> command;
-
-    if (command == "V8DEBUG") {
-        QByteArray request;
-        ds >> request;
-
-        QVariantMap reqMap;
-        /* Parse the byte string in a separate isolate
-        This will ensure that the debug message handler does not
-        receive any messages related to this operation */
-        {
-            v8::Isolate::Scope i_scope(d->isolate);
-            QString req(request);
-            QJSValue parser = d->engine->evaluate(QLatin1String("JSON.parse"));
-            QJSValue out = parser.call(QJSValue(), QJSValueList() << QJSValue(req));
-            reqMap = out.toVariant().toMap();
-        }
+    QByteArray header;
+    ds >> header;
+
+    if (header == "V8DEBUG") {
+        QByteArray command;
+        QByteArray data;
+        ds >> command >> data;
+
+        if (command == V8_DEBUGGER_KEY_CONNECT) {
+            QMutexLocker locker(&d->initializeMutex);
+            d->connectReceived = true;
+            sendMessage(QV8DebugServicePrivate::packMessage(QLatin1String(V8_DEBUGGER_KEY_CONNECT)));
+
+        } else if (command == V8_DEBUGGER_KEY_INTERRUPT) {
+            // break has to be executed in gui thread
+            QMetaObject::invokeMethod(this, "scheduledDebugBreak", Qt::QueuedConnection, Q_ARG(bool, true));
+            sendMessage(QV8DebugServicePrivate::packMessage(QLatin1String(V8_DEBUGGER_KEY_INTERRUPT)));
+
+        } else if (command == V8_DEBUGGER_KEY_DISCONNECT) {
+            // cancel break has to be executed in gui thread
+            QMetaObject::invokeMethod(this, "scheduledDebugBreak", Qt::QueuedConnection, Q_ARG(bool, false));
+            sendDebugMessage(QString::fromUtf8(data));
+
+        } else if (command == V8_DEBUGGER_KEY_REQUEST) {
+            sendDebugMessage(QString::fromUtf8(data));
+
+        } else if (command == V8_DEBUGGER_KEY_BREAK_ON_SIGNAL) {
+            QDataStream rs(data);
+            QByteArray signal;
+            bool enabled;
+            rs >> signal >> enabled;
+             //Normalize to lower case.
+            QString signalName(QString::fromUtf8(signal).toLower());
+            if (enabled)
+                d->breakOnSignals.append(signalName);
+            else
+                d->breakOnSignals.removeOne(signalName);
+            sendMessage(QV8DebugServicePrivate::packMessage(QLatin1String(V8_DEBUGGER_KEY_BREAK_ON_SIGNAL)));
+
+        } else if (command == V8_DEBUGGER_KEY_BREAK_AFTER_COMPILE) {
+            QDataStream rs(data);
+            rs >> d->breakAfterCompile;
+            sendMessage(QV8DebugServicePrivate::packMessage(QLatin1String(V8_DEBUGGER_KEY_BREAK_AFTER_COMPILE)));
 
-        QString debugCommand(reqMap.value(QLatin1String("command")).toString());
-
-        if (debugCommand == QLatin1String("connect")) {
-            d->initialized = true;
-
-        } else if (debugCommand == QLatin1String("interrupt")) {
-            v8::Debug::DebugBreak();
-
-        } else {
-            bool ok = true;
-
-            if (debugCommand == QLatin1String("setbreakpoint")){
-                QVariantMap arguments = reqMap.value(QLatin1String("arguments")).toMap();
-                QString type(arguments.value(QLatin1String("type")).toString());
-
-                if (type == QLatin1String("script")) {
-                    QString fileName(arguments.value(QLatin1String("target")).toString());
-
-                    //Check if the filepath has been cached
-                    if (d->sourcePath.contains(fileName)) {
-                        QString filePath = d->sourcePath.value(fileName);
-                        request.replace(fileName.toUtf8(), filePath.toUtf8());
-                    } else {
-                        //Store the setbreakpoint message till filepath is resolved
-                        d->requestCache.insertMulti(fileName, request);
-                        ok = false;
-                    }
-                }
-            }
-            if (ok)
-                sendDebugMessage(request);
         }
     }
+}
 
-    QDeclarativeDebugService::messageReceived(message);
+void QV8DebugService::sendDebugMessage(const QString &message)
+{
+    v8::Debug::SendCommand(message.utf16(), message.size());
 }
 
-void QV8DebugService::sendDebugMessage(const QByteArray &msg)
+void QV8DebugService::processDebugMessages()
 {
     Q_D(QV8DebugService);
-
-    QString message(msg);
-    if (d->loop.isRunning()) {
-        d->loop.exit();
-    }
-    v8::Debug::SendCommand(message.utf16(), message.size());
+    v8::HandleScope handleScope;
+    v8::Context::Scope contextScope(d->engine->context());
+    v8::Debug::ProcessDebugMessages();
 }
 
-QByteArray QV8DebugService::packMessage(QByteArray &message)
+QByteArray QV8DebugServicePrivate::packMessage(const QString &type, const QString &message)
 {
     QByteArray reply;
     QDataStream rs(&reply, QIODevice::WriteOnly);
     QByteArray cmd("V8DEBUG");
-    rs << cmd << message;
+    rs << cmd << type.toUtf8() << message.toUtf8();
     return reply;
 }