/****************************************************************************
**
-** 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();
}
}
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;
}