/************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** ** GNU Lesser General Public License Usage ** ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this file. ** Please review the following information to ensure the GNU Lesser General ** Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** Other Usage ** ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** **************************************************************************/ #include "qmlv8debuggerclient.h" #include "qmlv8debuggerclientconstants.h" #include "debuggerstringutils.h" #include "watchhandler.h" #include "breakpoint.h" #include "breakhandler.h" #include "qmlengine.h" #include "stackhandler.h" #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_QML 0 #if DEBUG_QML # define SDEBUG(s) qDebug() << s << '\n' #else # define SDEBUG(s) #endif using namespace Core; namespace Debugger { namespace Internal { typedef QPair WatchDataPair; struct QmlV8ObjectData { QByteArray type; QVariant value; QVariant properties; }; class QmlV8DebuggerClientPrivate { public: explicit QmlV8DebuggerClientPrivate(QmlV8DebuggerClient *q) : q(q), sequence(-1), engine(0), isOldService(false) { parser = m_scriptEngine.evaluate(_("JSON.parse")); stringifier = m_scriptEngine.evaluate(_("JSON.stringify")); } void connect(); void disconnect(); void interrupt(); void continueDebugging(QmlV8DebuggerClient::StepAction stepAction, int stepCount = 1); void evaluate(const QString expr, bool global = false, bool disableBreak = false, int frame = -1, bool addContext = false); void lookup(const QList handles, bool includeSource = false); void backtrace(int fromFrame = -1, int toFrame = -1, bool bottom = false); void frame(int number = -1); void scope(int number = -1, int frameNumber = -1); void scopes(int frameNumber = -1); void scripts(int types = 4, const QList ids = QList(), bool includeSource = false, const QVariant filter = QVariant()); void source(int frame = -1, int fromLine = -1, int toLine = -1); void setBreakpoint(const QString type, const QString target, int line = -1, int column = -1, bool enabled = true, const QString condition = QString(), int ignoreCount = -1); void changeBreakpoint(int breakpoint, bool enabled = true, const QString condition = QString(), int ignoreCount = -1); void clearBreakpoint(int breakpoint); void setExceptionBreak(QmlV8DebuggerClient::Exceptions type, bool enabled = false); void listBreakpoints(); void v8flags(const QString flags); void version(); //void profile(ProfileCommand command); //NOT SUPPORTED void gc(); QmlV8ObjectData extractData(const QVariant &data); void clearCache(); void logSendMessage(const QString &msg) const; void logReceiveMessage(const QString &msg) const; //TODO:: remove this method void reformatRequest(QByteArray &request); private: QByteArray packMessage(const QByteArray &type, const QByteArray &message = QByteArray()); QScriptValue initObject(); public: QmlV8DebuggerClient *q; int sequence; QmlEngine *engine; QHash breakpoints; QHash breakpointsSync; QList breakpointsTemp; QScriptValue parser; QScriptValue stringifier; QStringList scriptSourceRequests; QHash evaluatingExpression; QHash localsAndWatchers; //Cache QStringList watchedExpressions; QVariant refsVal; QList currentFrameScopes; //TODO: remove this flag bool isOldService; private: QScriptEngine m_scriptEngine; }; /////////////////////////////////////////////////////////////////////// // // QmlV8DebuggerClientPrivate // /////////////////////////////////////////////////////////////////////// void QmlV8DebuggerClientPrivate::connect() { logSendMessage(QString(_("%1 %2")).arg(_(V8DEBUG), _(CONNECT))); if (isOldService) { // { "seq" : , // "type" : "request", // "command" : "connect", // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(CONNECT))); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); q->sendMessage(packMessage(QByteArray(), jsonMessage.toString().toUtf8())); } else { q->sendMessage(packMessage(CONNECT)); } } void QmlV8DebuggerClientPrivate::disconnect() { // { "seq" : , // "type" : "request", // "command" : "disconnect", // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(DISCONNECT))); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2")).arg(_(V8DEBUG), jsonMessage.toString())); q->sendMessage(packMessage(DISCONNECT, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::interrupt() { logSendMessage(QString(_("%1 %2")).arg(_(V8DEBUG), _(INTERRUPT))); if (isOldService) { // { "seq" : , // "type" : "request", // "command" : "interrupt", // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(INTERRUPT))); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); q->sendMessage(packMessage(QByteArray(), jsonMessage.toString().toUtf8())); } else { q->sendMessage(packMessage(INTERRUPT)); } } void QmlV8DebuggerClientPrivate::continueDebugging(QmlV8DebuggerClient::StepAction action, int count) { // { "seq" : , // "type" : "request", // "command" : "continue", // "arguments" : { "stepaction" : <"in", "next" or "out">, // "stepcount" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(CONTINEDEBUGGING))); if (action != QmlV8DebuggerClient::Continue) { QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); switch (action) { case QmlV8DebuggerClient::In: args.setProperty(_(STEPACTION), QScriptValue(_(IN))); break; case QmlV8DebuggerClient::Out: args.setProperty(_(STEPACTION), QScriptValue(_(OUT))); break; case QmlV8DebuggerClient::Next: args.setProperty(_(STEPACTION), QScriptValue(_(NEXT))); break; default:break; } if (count != 1) args.setProperty(_(STEPCOUNT), QScriptValue(count)); jsonVal.setProperty(_(ARGUMENTS), args); } const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::evaluate(const QString expr, bool global, bool disableBreak, int frame, bool addContext) { // { "seq" : , // "type" : "request", // "command" : "evaluate", // "arguments" : { "expression" : , // "frame" : , // "global" : , // "disable_break" : , // "additional_context" : [ // { "name" : , "handle" : }, // { "name" : , "handle" : }, // ... // ] // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(EVALUATE))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(EXPRESSION), QScriptValue(expr)); if (frame != -1) args.setProperty(_(FRAME), QScriptValue(frame)); if (global) args.setProperty(_(GLOBAL), QScriptValue(global)); if (disableBreak) args.setProperty(_(DISABLE_BREAK), QScriptValue(disableBreak)); if (addContext) { QAbstractItemModel *localsModel = engine->localsModel(); int rowCount = localsModel->rowCount(); QScriptValue ctxtList = parser.call(QScriptValue(), QScriptValueList() << _(ARRAY )); while (rowCount) { QModelIndex index = localsModel->index(--rowCount, 0); const WatchData *data = engine->watchHandler()->watchData(LocalsWatch, index); QScriptValue ctxt = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); ctxt.setProperty(_(NAME), QScriptValue(data->name)); ctxt.setProperty(_(HANDLE), QScriptValue(int(data->id))); ctxtList.setProperty(rowCount, ctxt); } args.setProperty(_(ADDITIONAL_CONTEXT), QScriptValue(ctxtList)); } jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::lookup(QList handles, bool includeSource) { // { "seq" : , // "type" : "request", // "command" : "lookup", // "arguments" : { "handles" : , // "includeSource" : , // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(LOOKUP))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); QScriptValue array = parser.call(QScriptValue(), QScriptValueList() << _(ARRAY)); int index = 0; foreach (int handle, handles) { array.setProperty(index++, QScriptValue(handle)); } args.setProperty(_(HANDLES), array); if (includeSource) args.setProperty(_(INCLUDESOURCE), QScriptValue(includeSource)); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::backtrace(int fromFrame, int toFrame, bool bottom) { // { "seq" : , // "type" : "request", // "command" : "backtrace", // "arguments" : { "fromFrame" : // "toFrame" : // "bottom" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(BACKTRACE))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); if (fromFrame != -1) args.setProperty(_(FROMFRAME), QScriptValue(fromFrame)); if (toFrame != -1) args.setProperty(_(TOFRAME), QScriptValue(toFrame)); if (bottom) args.setProperty(_(BOTTOM), QScriptValue(bottom)); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::frame(int number) { // { "seq" : , // "type" : "request", // "command" : "frame", // "arguments" : { "number" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(FRAME))); if (number != -1) { QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(NUMBER), QScriptValue(number)); jsonVal.setProperty(_(ARGUMENTS), args); } const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::scope(int number, int frameNumber) { // { "seq" : , // "type" : "request", // "command" : "scope", // "arguments" : { "number" : // "frameNumber" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(SCOPE))); if (number != -1) { QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(NUMBER), QScriptValue(number)); if (frameNumber != -1) args.setProperty(_(FRAMENUMBER), QScriptValue(frameNumber)); jsonVal.setProperty(_(ARGUMENTS), args); } const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::scopes(int frameNumber) { // { "seq" : , // "type" : "request", // "command" : "scopes", // "arguments" : { "frameNumber" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(SCOPES))); if (frameNumber != -1) { QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(FRAMENUMBER), QScriptValue(frameNumber)); jsonVal.setProperty(_(ARGUMENTS), args); } const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::scripts(int types, const QList ids, bool includeSource, const QVariant filter) { // { "seq" : , // "type" : "request", // "command" : "scripts", // "arguments" : { "types" : // "ids" : // "includeSource" : // "filter" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(SCRIPTS))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(TYPES), QScriptValue(types)); if (ids.count()) { QScriptValue array = parser.call(QScriptValue(), QScriptValueList() << _(ARRAY)); int index = 0; foreach (int id, ids) { array.setProperty(index++, QScriptValue(id)); } args.setProperty(_(IDS), array); } if (includeSource) args.setProperty(_(INCLUDESOURCE), QScriptValue(includeSource)); QScriptValue filterValue; if (filter.type() == QVariant::String) filterValue = QScriptValue(filter.toString()); else if (filter.type() == QVariant::Int) filterValue = QScriptValue(filter.toInt()); else QTC_CHECK(!filter.isValid()); args.setProperty(_(FILTER), filterValue); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::source(int frame, int fromLine, int toLine) { // { "seq" : , // "type" : "request", // "command" : "source", // "arguments" : { "frame" : // "fromLine" : // "toLine" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(SOURCE))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); if (frame != -1) args.setProperty(_(FRAME), QScriptValue(frame)); if (fromLine != -1) args.setProperty(_(FROMLINE), QScriptValue(fromLine)); if (toLine != -1) args.setProperty(_(TOLINE), QScriptValue(toLine)); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::setBreakpoint(const QString type, const QString target, int line, int column, bool enabled, const QString condition, int ignoreCount) { // { "seq" : , // "type" : "request", // "command" : "setbreakpoint", // "arguments" : { "type" : <"function" or "script" or "scriptId" or "scriptRegExp"> // "target" : // "line" : // "column" : // "enabled" : // "condition" : // "ignoreCount" : // } // } if (type == _(EVENT) && !isOldService) { QByteArray params; QDataStream rs(¶ms, QIODevice::WriteOnly); rs << target.toUtf8() << enabled; logSendMessage(QString(_("%1 %2 %3 %4")).arg(_(V8DEBUG), _(SIGNALHANDLER), target, enabled?_("enabled"):_("disabled"))); q->sendMessage(packMessage(SIGNALHANDLER, params)); } else { QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(SETBREAKPOINT))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(TYPE), QScriptValue(type)); args.setProperty(_(TARGET), QScriptValue(target)); if (line != -1) args.setProperty(_(LINE), QScriptValue(line)); if (column != -1) args.setProperty(_(COLUMN), QScriptValue(column)); args.setProperty(_(ENABLED), QScriptValue(enabled)); if (!condition.isEmpty()) args.setProperty(_(CONDITION), QScriptValue(condition)); if (ignoreCount != -1) args.setProperty(_(IGNORECOUNT), QScriptValue(ignoreCount)); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } } void QmlV8DebuggerClientPrivate::changeBreakpoint(int breakpoint, bool enabled, const QString condition, int ignoreCount) { // { "seq" : , // "type" : "request", // "command" : "changebreakpoint", // "arguments" : { "breakpoint" : // "enabled" : // "condition" : // "ignoreCount" : sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::clearBreakpoint(int breakpoint) { // { "seq" : , // "type" : "request", // "command" : "clearbreakpoint", // "arguments" : { "breakpoint" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(CLEARBREAKPOINT))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(BREAKPOINT), QScriptValue(breakpoint)); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::setExceptionBreak(QmlV8DebuggerClient::Exceptions type, bool enabled) { // { "seq" : , // "type" : "request", // "command" : "setexceptionbreak", // "arguments" : { "type" : , // "enabled" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(SETEXCEPTIONBREAK))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); if (type == QmlV8DebuggerClient::AllExceptions) args.setProperty(_(TYPE), QScriptValue(_(ALL))); //Not Supported // else if (type == QmlV8DebuggerClient::UncaughtExceptions) // args.setProperty(_(TYPE),QScriptValue(_(UNCAUGHT))); if (enabled) args.setProperty(_(ENABLED), QScriptValue(enabled)); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::listBreakpoints() { // { "seq" : , // "type" : "request", // "command" : "listbreakpoints", // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(LISTBREAKPOINTS))); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::v8flags(const QString flags) { // { "seq" : , // "type" : "request", // "command" : "v8flags", // "arguments" : { "flags" : // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(V8FLAGS))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(FLAGS), QScriptValue(flags)); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } void QmlV8DebuggerClientPrivate::version() { // { "seq" : , // "type" : "request", // "command" : "version", // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(VERSION))); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } //void QmlV8DebuggerClientPrivate::profile(ProfileCommand command) //{ //// { "seq" : , //// "type" : "request", //// "command" : "profile", //// "arguments" : { "command" : "resume" or "pause" } //// } // QScriptValue jsonVal = initObject(); // jsonVal.setProperty(_(COMMAND), QScriptValue(_(PROFILE))); // QScriptValue args = m_parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); // if (command == Resume) // args.setProperty(_(COMMAND), QScriptValue(_(RESUME))); // else // args.setProperty(_(COMMAND), QScriptValue(_(PAUSE))); // args.setProperty(_("modules"), QScriptValue(1)); // jsonVal.setProperty(_(ARGUMENTS), args); // const QScriptValue jsonMessage = m_stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); // logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); // q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); //} void QmlV8DebuggerClientPrivate::gc() { // { "seq" : , // "type" : "request", // "command" : "gc", // "arguments" : { "type" : , // } // } QScriptValue jsonVal = initObject(); jsonVal.setProperty(_(COMMAND), QScriptValue(_(GARBAGECOLLECTOR))); QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); args.setProperty(_(TYPE), QScriptValue(_(ALL))); jsonVal.setProperty(_(ARGUMENTS), args); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); logSendMessage(QString(_("%1 %2 %3")).arg(_(V8DEBUG), _(V8REQUEST), jsonMessage.toString())); q->sendMessage(packMessage(V8REQUEST, jsonMessage.toString().toUtf8())); } QmlV8ObjectData QmlV8DebuggerClientPrivate::extractData(const QVariant &data) { // { "handle" : , // "type" : <"undefined", "null", "boolean", "number", "string", "object", "function" or "frame"> // } // {"handle":,"type":"undefined"} // {"handle":,"type":"null"} // { "handle":, // "type" : <"boolean", "number" or "string"> // "value" : // } // {"handle":7,"type":"boolean","value":true} // {"handle":8,"type":"number","value":42} // { "handle" : , // "type" : "object", // "className" : , // "constructorFunction" : {"ref":}, // "protoObject" : {"ref":}, // "prototypeObject" : {"ref":}, // "properties" : [ {"name" : , // "ref" : // }, // ... // ] // } // { "handle" : , // "type" : "function", // "className" : "Function", // "constructorFunction" : {"ref":}, // "protoObject" : {"ref":}, // "prototypeObject" : {"ref":}, // "name" : , // "inferredName" : // "source" : , // "script" : , // "scriptId" : , // "position" : , // "line" : , // "column" : , // "properties" : [ {"name" : , // "ref" : // }, // ... // ] // } QmlV8ObjectData objectData; const QVariantMap dataMap = data.toMap(); QString type = dataMap.value(_(TYPE)).toString(); if (type == _("undefined")) { objectData.type = QByteArray("undefined"); objectData.value = QVariant(_("undefined")); } else if (type == _("null")) { objectData.type = QByteArray("null"); objectData.value= QVariant(_("null")); } else if (type == _("boolean")) { objectData.type = QByteArray("boolean"); objectData.value = dataMap.value(_(VALUE)); } else if (type == _("number")) { objectData.type = QByteArray("number"); objectData.value = dataMap.value(_(VALUE)); } else if (type == _("string")) { objectData.type = QByteArray("string"); objectData.value = dataMap.value(_(VALUE)); } else if (type == _("object")) { objectData.type = QByteArray("object"); objectData.value = dataMap.value(_("className")); objectData.properties = dataMap.value(_("properties")); } else if (type == _("function")) { objectData.type = QByteArray("function"); objectData.value = dataMap.value(_(NAME)); objectData.properties = dataMap.value(_("properties")); } else if (type == _("script")) { objectData.type = QByteArray("script"); objectData.value = dataMap.value(_(NAME)); } return objectData; } void QmlV8DebuggerClientPrivate::clearCache() { watchedExpressions.clear(); refsVal.clear(); currentFrameScopes.clear(); } QByteArray QmlV8DebuggerClientPrivate::packMessage(const QByteArray &type, const QByteArray &message) { SDEBUG(message); QByteArray request; QDataStream rs(&request, QIODevice::WriteOnly); QByteArray cmd = V8DEBUG; rs << cmd; if (!isOldService) rs << type; rs << message; return request; } QScriptValue QmlV8DebuggerClientPrivate::initObject() { QScriptValue jsonVal = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); jsonVal.setProperty(_(SEQ), QScriptValue(++sequence)); jsonVal.setProperty(_(TYPE), _(REQUEST)); return jsonVal; } void QmlV8DebuggerClientPrivate::logSendMessage(const QString &msg) const { if (engine) engine->logMessage(QLatin1String("V8DebuggerClient"), QmlEngine::LogSend, msg); } void QmlV8DebuggerClientPrivate::logReceiveMessage(const QString &msg) const { if (engine) engine->logMessage(QLatin1String("V8DebuggerClient"), QmlEngine::LogReceive, msg); } //TODO::remove this method void QmlV8DebuggerClientPrivate::reformatRequest(QByteArray &request) { QDataStream ds(request); QByteArray header; ds >> header; if (header == "V8DEBUG") { QByteArray command; QByteArray data; ds >> command >> data; if (command == INTERRUPT) { interrupt(); } else if (command == V8REQUEST) { const QString requestString = QLatin1String(data); const QVariantMap reqMap = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(requestString)).toVariant().toMap(); const QString debugCommand(reqMap.value(_(COMMAND)).toString()); if (debugCommand == _(SETBREAKPOINT)) { QVariantMap arguments = reqMap.value(_(ARGUMENTS)).toMap(); QString type(arguments.value(_(TYPE)).toString()); if (type == _(SCRIPTREGEXP)) { data.replace(SCRIPTREGEXP, SCRIPT); } } q->sendMessage(packMessage(QByteArray(), data)); } else if (command == SIGNALHANDLER) { QDataStream rs(data); QByteArray signalHandler; bool enabled; rs >> signalHandler >> enabled; setBreakpoint(_(EVENT), QString::fromUtf8(signalHandler), -1, -1, enabled); } } } /////////////////////////////////////////////////////////////////////// // // QmlV8DebuggerClient // /////////////////////////////////////////////////////////////////////// QmlV8DebuggerClient::QmlV8DebuggerClient(QmlJsDebugClient::QDeclarativeDebugConnection *client) : QmlDebuggerClient(client, QLatin1String("V8Debugger")), d(new QmlV8DebuggerClientPrivate(this)) { } QmlV8DebuggerClient::~QmlV8DebuggerClient() { delete d; } void QmlV8DebuggerClient::startSession() { //TODO:: remove this check. //Modify messages to align with the older protocol if (serviceVersion() < CURRENT_SUPPORTED_VERSION) { d->isOldService = true; //Modify cached messages QList buffer = cachedMessages(); clearCachedMessages(); foreach (QByteArray msg, buffer) { d->reformatRequest(msg); } } flushSendBuffer(); d->connect(); //Query for the V8 version. This is //only for logging to the debuggerlog d->version(); } void QmlV8DebuggerClient::endSession() { d->disconnect(); } void QmlV8DebuggerClient::executeStep() { clearExceptionSelection(); d->continueDebugging(In); } void QmlV8DebuggerClient::executeStepOut() { clearExceptionSelection(); d->continueDebugging(Out); } void QmlV8DebuggerClient::executeNext() { clearExceptionSelection(); d->continueDebugging(Next); } void QmlV8DebuggerClient::executeStepI() { clearExceptionSelection(); d->continueDebugging(In); } void QmlV8DebuggerClient::executeRunToLine(const ContextData &data) { if (d->isOldService) { d->setBreakpoint(QString(_(SCRIPT)), QFileInfo(data.fileName).fileName(), data.lineNumber - 1); } else { d->setBreakpoint(QString(_(SCRIPTREGEXP)), QFileInfo(data.fileName).fileName(), data.lineNumber - 1); } clearExceptionSelection(); d->continueDebugging(Continue); } void QmlV8DebuggerClient::continueInferior() { clearExceptionSelection(); d->continueDebugging(Continue); } void QmlV8DebuggerClient::interruptInferior() { d->interrupt(); } void QmlV8DebuggerClient::activateFrame(int index) { if (index != d->engine->stackHandler()->currentIndex()) d->frame(index); } bool QmlV8DebuggerClient::acceptsBreakpoint(const BreakpointModelId &id) { BreakpointType type = d->engine->breakHandler()->breakpointData(id).type; return (type == BreakpointOnQmlSignalHandler || type == BreakpointByFileAndLine || type == BreakpointAtJavaScriptThrow); } void QmlV8DebuggerClient::insertBreakpoint(const BreakpointModelId &id) { BreakHandler *handler = d->engine->breakHandler(); const BreakpointParameters ¶ms = handler->breakpointData(id); if (params.type == BreakpointAtJavaScriptThrow) { handler->notifyBreakpointInsertOk(id); d->setExceptionBreak(AllExceptions, params.enabled); } else if (params.type == BreakpointByFileAndLine) { if (d->isOldService) { d->setBreakpoint(QString(_(SCRIPT)), QFileInfo(params.fileName).fileName(), params.lineNumber - 1, -1, params.enabled, QLatin1String(params.condition), params.ignoreCount); } else { d->setBreakpoint(QString(_(SCRIPTREGEXP)), QFileInfo(params.fileName).fileName(), params.lineNumber - 1, -1, params.enabled, QLatin1String(params.condition), params.ignoreCount); } } else if (params.type == BreakpointOnQmlSignalHandler) { d->setBreakpoint(QString(_(EVENT)), params.functionName, -1, -1, params.enabled); d->engine->breakHandler()->notifyBreakpointInsertOk(id); } d->breakpointsSync.insert(d->sequence, id); } void QmlV8DebuggerClient::removeBreakpoint(const BreakpointModelId &id) { BreakHandler *handler = d->engine->breakHandler(); int breakpoint = d->breakpoints.value(id); d->breakpoints.remove(id); if (handler->breakpointData(id).type == BreakpointAtJavaScriptThrow) { d->setExceptionBreak(AllExceptions); } else if (handler->breakpointData(id).type == BreakpointOnQmlSignalHandler) { d->setBreakpoint(QString(_(EVENT)), handler->breakpointData(id).functionName, -1, -1, false); } else { d->clearBreakpoint(breakpoint); } } void QmlV8DebuggerClient::changeBreakpoint(const BreakpointModelId &id) { BreakHandler *handler = d->engine->breakHandler(); const BreakpointParameters ¶ms = handler->breakpointData(id); if (params.type == BreakpointAtJavaScriptThrow) { d->setExceptionBreak(AllExceptions, params.enabled); } else if (handler->breakpointData(id).type == BreakpointOnQmlSignalHandler) { d->setBreakpoint(QString(_(EVENT)), params.functionName, -1, -1, params.enabled); } else { int breakpoint = d->breakpoints.value(id); d->changeBreakpoint(breakpoint, params.enabled, QLatin1String(params.condition), params.ignoreCount); } BreakpointResponse br = handler->response(id); br.enabled = params.enabled; br.condition = params.condition; br.ignoreCount = params.ignoreCount; handler->setResponse(id, br); } void QmlV8DebuggerClient::synchronizeBreakpoints() { //NOT USED } void QmlV8DebuggerClient::assignValueInDebugger(const QByteArray /*expr*/, const quint64 &/*id*/, const QString &property, const QString &value) { StackHandler *stackHandler = d->engine->stackHandler(); QString expression = QString(_("%1 = %2;")).arg(property).arg(value); if (stackHandler->isContentsValid() && stackHandler->currentFrame().isUsable()) { d->evaluate(expression, false, false, stackHandler->currentIndex()); } else { d->engine->showMessage(QString(_("Cannot evaluate %1 in current stack frame")).arg(expression), ScriptConsoleOutput); } } void QmlV8DebuggerClient::updateWatchData(const WatchData &/*data*/) { //NOT USED } void QmlV8DebuggerClient::executeDebuggerCommand(const QString &command) { StackHandler *stackHandler = d->engine->stackHandler(); if (stackHandler->isContentsValid() && stackHandler->currentFrame().isUsable()) { d->evaluate(command, false, false, stackHandler->currentIndex()); d->evaluatingExpression.insert(d->sequence, command); } else { //Currently cannot evaluate if not in a javascript break d->engine->showMessage(QString(_("Cannot evaluate %1 in current stack frame")).arg(command), ScriptConsoleOutput); // d->evaluate(command); } } void QmlV8DebuggerClient::synchronizeWatchers(const QStringList &watchers) { SDEBUG(watchers); foreach (const QString &exp, watchers) { if (!d->watchedExpressions.contains(exp)) { d->watchedExpressions << exp; StackHandler *stackHandler = d->engine->stackHandler(); if (stackHandler->isContentsValid() && stackHandler->currentFrame().isUsable()) { d->evaluate(exp, false, false, stackHandler->currentIndex()); d->evaluatingExpression.insert(d->sequence, exp); } } } } void QmlV8DebuggerClient::expandObject(const QByteArray &iname, quint64 objectId) { d->localsAndWatchers.insert(objectId, iname); d->lookup(QList() << objectId); } void QmlV8DebuggerClient::setEngine(QmlEngine *engine) { d->engine = engine; } void QmlV8DebuggerClient::getSourceFiles() { d->scripts(4, QList(), true, QVariant()); } void QmlV8DebuggerClient::messageReceived(const QByteArray &data) { QDataStream ds(data); QByteArray command; ds >> command; if (command == V8DEBUG) { QByteArray type; QByteArray response; ds >> type >> response; if (d->isOldService) { response = type; type = QByteArray(V8MESSAGE); } d->logReceiveMessage(_(V8DEBUG) + QLatin1Char(' ') + QLatin1String(type)); if (type == CONNECT) { //debugging session started } else if (type == INTERRUPT) { //debug break requested } else if (type == SIGNALHANDLER) { //break on signal handler requested } else if (type == V8MESSAGE) { const QString responseString = QLatin1String(response); SDEBUG(responseString); d->logReceiveMessage(QLatin1String(V8MESSAGE) + QLatin1Char(' ') + responseString); const QVariantMap resp = d->parser.call(QScriptValue(), QScriptValueList() << QScriptValue(responseString)).toVariant().toMap(); const QString type(resp.value(_(TYPE)).toString()); if (type == _("response")) { bool success = resp.value(_("success")).toBool(); if (!success) { SDEBUG("Request was unsuccessful"); } const QString debugCommand(resp.value(_(COMMAND)).toString()); if (debugCommand == _(DISCONNECT)) { //debugging session ended } else if (debugCommand == _(CONTINEDEBUGGING)) { //do nothing, wait for next break } else if (debugCommand == _(BACKTRACE)) { if (success) { updateStack(resp.value(_(BODY)), resp.value(_(REFS))); } } else if (debugCommand == _(LOOKUP)) { if (success) { expandLocalsAndWatchers(resp.value(_(BODY)), resp.value(_(REFS))); } } else if (debugCommand == _(EVALUATE)) { int seq = resp.value(_("request_seq")).toInt(); if (success) { updateEvaluationResult(seq, success, resp.value(_(BODY)), resp.value(_(REFS))); } else { QVariantMap map; map.insert(_(TYPE), QVariant(_("string"))); map.insert(_(VALUE), resp.value(_("message"))); updateEvaluationResult(seq, success, QVariant(map), QVariant()); } } else if (debugCommand == _(LISTBREAKPOINTS)) { if (success) { updateBreakpoints(resp.value(_(BODY))); } } else if (debugCommand == _(SETBREAKPOINT)) { // { "seq" : , // "type" : "response", // "request_seq" : , // "command" : "setbreakpoint", // "body" : { "type" : <"function" or "script"> // "breakpoint" : // } // "running" : // "success" : true // } int seq = resp.value(_("request_seq")).toInt(); const QVariantMap breakpointData = resp.value(_(BODY)).toMap(); int index = breakpointData.value(_("breakpoint")).toInt(); if (d->breakpointsSync.contains(seq)) { BreakpointModelId id = d->breakpointsSync.take(seq); d->breakpoints.insert(id, index); if (d->engine->breakHandler()->state(id) != BreakpointInserted) d->engine->breakHandler()->notifyBreakpointInsertOk(id); } else { d->breakpointsTemp.append(index); } } else if (debugCommand == _(CHANGEBREAKPOINT)) { // DO NOTHING } else if (debugCommand == _(CLEARBREAKPOINT)) { // DO NOTHING } else if (debugCommand == _(SETEXCEPTIONBREAK)) { // { "seq" : , // "type" : "response", // "request_seq" : , // "command" : "setexceptionbreak", // "body" : { "type" : , // "enabled" : // } // "running" : true // "success" : true // } } else if (debugCommand == _(FRAME)) { if (success) { setCurrentFrameDetails(resp.value(_(BODY)), resp.value(_(REFS))); } } else if (debugCommand == _(SCOPE)) { if (success) { updateScope(resp.value(_(BODY)), resp.value(_(REFS))); } } else if (debugCommand == _(SCOPES)) { } else if (debugCommand == _(SOURCE)) { } else if (debugCommand == _(SCRIPTS)) { // { "seq" : , // "type" : "response", // "request_seq" : , // "command" : "scripts", // "body" : [ { "name" : , // "id" : // "lineOffset" : // "columnOffset" : // "lineCount" : // "data" : // "source" : // "sourceStart" : // "sourceLength" : // "scriptType" :