1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qv8debugservice_p.h"
43 #include "qdeclarativedebugservice_p_p.h"
44 #include <private/qv8debug_p.h>
45 #include <private/qv8engine_p.h>
46 #include <private/qdeclarativeengine_p.h>
48 #include <QtCore/QEventLoop>
49 #include <QtCore/QHash>
50 #include <QtCore/QFileInfo>
54 struct SignalHandlerData
60 Q_GLOBAL_STATIC(QV8DebugService, v8ServiceInstance)
62 void DebugMessageHandler(const v8::Debug::Message& message)
64 v8::DebugEvent event = message.GetEvent();
66 if (event != v8::Break && event != v8::Exception &&
67 event != v8::AfterCompile && event != v8::BeforeCompile) {
71 const QString response(QV8Engine::toStringStatic(
74 QV8DebugService *service = QV8DebugService::instance();
75 service->debugMessageHandler(response);
77 if ((event == v8::Break || event == v8::Exception) &&
78 !message.WillStartRunning()) {
79 service->executionStopped();
80 } else if (event == v8::AfterCompile) {
81 service->appendSourcePath(response);
82 } //TODO::v8::Exception
85 class QV8DebugServicePrivate : public QDeclarativeDebugServicePrivate
88 QV8DebugServicePrivate()
91 //Create a new isolate
92 isolate = v8::Isolate::New();
94 //Enter the isolate and initialize
95 v8::Isolate::Scope i_scope(isolate);
98 //Create an instance in the new isolate
99 engine = new QJSEngine();
102 ~QV8DebugServicePrivate()
108 void sendDebugMessage(const QString &message);
109 static QByteArray packMessage(const QString &message);
113 v8::Isolate *isolate;
114 QList<QDeclarativeEngine *> engines;
116 QHash<QString, QString> sourcePath;
117 QHash<QString, QString> requestCache;
118 QHash<int, SignalHandlerData> handlersList;
121 QV8DebugService::QV8DebugService(QObject *parent)
122 : QDeclarativeDebugService(*(new QV8DebugServicePrivate()),
123 QLatin1String("V8Debugger"), parent)
125 Q_D(QV8DebugService);
126 v8::Debug::SetMessageHandler2(DebugMessageHandler);
127 if (status() == Enabled) {
128 // ,block mode, client attached
129 while (!d->initialized) {
135 QV8DebugService::~QV8DebugService()
139 QV8DebugService *QV8DebugService::instance()
141 return v8ServiceInstance();
144 void QV8DebugService::addEngine(QDeclarativeEngine *engine)
146 Q_D(QV8DebugService);
148 Q_ASSERT(!d->engines.contains(engine));
150 d->engines.append(engine);
153 void QV8DebugService::removeEngine(QDeclarativeEngine *engine)
155 Q_D(QV8DebugService);
157 Q_ASSERT(d->engines.contains(engine));
159 d->engines.removeAll(engine);
162 void QV8DebugService::debugMessageHandler(const QString &message)
164 sendMessage(QV8DebugServicePrivate::packMessage(message));
167 void QV8DebugService::executionStopped()
169 Q_D(QV8DebugService);
171 if (!d->loop.isRunning()) {
172 d->loop.exec(QEventLoop::ExcludeUserInputEvents);
176 void QV8DebugService::appendSourcePath(const QString &message)
178 Q_D(QV8DebugService);
181 /* Parse the byte string in a separate isolate
182 This will ensure that the debug message handler does not
183 receive any messages related to this operation */
185 v8::Isolate::Scope i_scope(d->isolate);
186 QJSValue parser = d->engine->evaluate(QLatin1String("JSON.parse"));
187 QJSValue out = parser.call(QJSValue(), QJSValueList() << QJSValue(message));
188 msgMap = out.toVariant().toMap();
191 const QString sourcePath(msgMap.value(QLatin1String("body")).toMap().value(
192 QLatin1String("script")).toMap().value(
193 QLatin1String("name")).toString());
194 const QString fileName(QFileInfo(sourcePath).fileName());
196 d->sourcePath.insert(fileName, sourcePath);
198 //Check if there are any pending breakpoint requests for this file
199 if (d->requestCache.contains(fileName)) {
200 QList<QString> list = d->requestCache.values(fileName);
201 d->requestCache.remove(fileName);
202 foreach (QString request, list) {
203 request.replace(fileName, sourcePath);
204 d->sendDebugMessage(request);
209 void QV8DebugService::signalEmitted(const QString &signal)
211 //This function is only called by QDeclarativeBoundSignal
212 //only if there is a slot connected to the signal. Hence, there
213 //is no need for additional check.
214 Q_D(QV8DebugService);
216 bool debugBreak = false;
217 //Parse just the name and remove the class info
218 //Normalize to Lower case.
219 QString signalName = signal.left(signal.indexOf(QLatin1String("("))).toLower();
220 foreach (const SignalHandlerData &data, d->handlersList) {
221 if (data.functionName == signalName
227 v8::Debug::DebugBreak();
230 void QV8DebugService::messageReceived(const QByteArray &message)
232 Q_D(QV8DebugService);
234 QDataStream ds(message);
238 if (command == "V8DEBUG") {
241 QByteArray requestArray;
243 request = QString::fromUtf8(requestArray);
247 /* Parse the byte string in a separate isolate
248 This will ensure that the debug message handler does not
249 receive any messages related to this operation */
251 v8::Isolate::Scope i_scope(d->isolate);
252 QJSValue parser = d->engine->evaluate(QLatin1String("JSON.parse"));
253 QJSValue out = parser.call(QJSValue(), QJSValueList() << QJSValue(request));
254 reqMap = out.toVariant().toMap();
257 const QString debugCommand(reqMap.value(QLatin1String("command")).toString());
259 if (debugCommand == QLatin1String("connect")) {
260 d->initialized = true;
262 } else if (debugCommand == QLatin1String("interrupt")) {
263 v8::Debug::DebugBreak();
266 bool forwardRequestToV8 = true;
268 if (debugCommand == QLatin1String("setbreakpoint")) {
269 const QVariantMap arguments = reqMap.value(QLatin1String("arguments")).toMap();
270 const QString type(arguments.value(QLatin1String("type")).toString());
272 if (type == QLatin1String("script")) {
273 QString fileName(arguments.value(QLatin1String("target")).toString());
275 //Check if the filepath has been cached
276 if (d->sourcePath.contains(fileName)) {
277 QString filePath = d->sourcePath.value(fileName);
278 request.replace(fileName, filePath);
280 //Store the setbreakpoint message till filepath is resolved
281 d->requestCache.insertMulti(fileName, request);
282 forwardRequestToV8 = false;
284 } else if (type == QLatin1String("event")) {
285 //Do not send this request to v8
286 forwardRequestToV8 = false;
288 //Prepare the response string
289 //Create a json message using v8 debugging protocol
290 //and send it to client
292 // { "seq" : <number>,
293 // "type" : "response",
294 // "request_seq" : <number>,
295 // "command" : "setbreakpoint",
296 // "body" : { "type" : <"function" or "script">
297 // "breakpoint" : <break point number of the new break point>
299 // "running" : <is the VM running after sending this response>
303 v8::Isolate::Scope i_scope(d->isolate);
304 const QString obj(QLatin1String("{}"));
305 QJSValue parser = d->engine->evaluate(QLatin1String("JSON.parse"));
306 QJSValue jsonVal = parser.call(QJSValue(), QJSValueList() << obj);
307 jsonVal.setProperty(QLatin1String("type"), QJSValue(QLatin1String("response")));
309 const int sequence = reqMap.value(QLatin1String("seq")).toInt();
310 jsonVal.setProperty(QLatin1String("request_seq"), QJSValue(sequence));
311 jsonVal.setProperty(QLatin1String("command"), QJSValue(debugCommand));
313 //Check that the function starts with 'on'
314 QString eventName(arguments.value(QLatin1String("target")).toString());
317 if (eventName.startsWith(QLatin1String("on"))) {
318 SignalHandlerData data;
319 //Only store the probable signal name.
320 //Normalize to lower case.
321 data.functionName = eventName.remove(0,2).toLower();
322 data.enabled = arguments.value(QLatin1String("enabled")).toBool();
323 d->handlersList.insert(-sequence, data);
325 QJSValue args = parser.call(QJSValue(), QJSValueList() << obj);
327 args.setProperty(QLatin1String("type"), QJSValue(QLatin1String("event")));
328 args.setProperty(QLatin1String("breakpoint"), QJSValue(-sequence));
330 jsonVal.setProperty(QLatin1String("body"), args);
331 jsonVal.setProperty(QLatin1String("success"), QJSValue(true));
334 jsonVal.setProperty(QLatin1String("success"), QJSValue(false));
338 jsonVal.setProperty(QLatin1String("running"), QJSValue(!d->loop.isRunning()));
340 QJSValue stringify = d->engine->evaluate(QLatin1String("JSON.stringify"));
341 QJSValue json = stringify.call(QJSValue(), QJSValueList() << jsonVal);
342 debugMessageHandler(json.toString());
346 } else if (debugCommand == QLatin1String("changebreakpoint")) {
347 //check if the breakpoint is a negative integer (event breakpoint)
348 const QVariantMap arguments = reqMap.value(QLatin1String("arguments")).toMap();
349 const int bp = arguments.value(QLatin1String("breakpoint")).toInt();
352 SignalHandlerData data = d->handlersList.value(bp);
353 data.enabled = arguments.value(QLatin1String("enabled")).toBool();
354 d->handlersList.insert(bp, data);
355 forwardRequestToV8 = false;
357 } else if (debugCommand == QLatin1String("clearbreakpoint")) {
358 //check if the breakpoint is a negative integer (event breakpoint)
359 const QVariantMap arguments = reqMap.value(QLatin1String("arguments")).toMap();
360 const int bp = arguments.value(QLatin1String("breakpoint")).toInt();
363 d->handlersList.remove(bp);
364 forwardRequestToV8 = false;
367 if (forwardRequestToV8)
368 d->sendDebugMessage(request);
372 QDeclarativeDebugService::messageReceived(message);
375 void QV8DebugServicePrivate::sendDebugMessage(const QString &message)
377 if (loop.isRunning())
380 v8::Debug::SendCommand(message.utf16(), message.size());
383 QByteArray QV8DebugServicePrivate::packMessage(const QString &message)
386 QDataStream rs(&reply, QIODevice::WriteOnly);
387 QByteArray cmd("V8DEBUG");
388 rs << cmd << message.toUtf8();