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/QHash>
49 #include <QtCore/QFileInfo>
53 struct SignalHandlerData
59 Q_GLOBAL_STATIC(QV8DebugService, v8ServiceInstance)
61 void DebugMessageHandler(const v8::Debug::Message& message)
63 v8::DebugEvent event = message.GetEvent();
65 if (event != v8::Break && event != v8::Exception &&
66 event != v8::AfterCompile && event != v8::BeforeCompile) {
70 const QString response(QV8Engine::toStringStatic(
73 QV8DebugService *service = QV8DebugService::instance();
74 service->debugMessageHandler(response, message.WillStartRunning());
76 if (event == v8::AfterCompile) {
77 service->appendSourcePath(response);
78 } //TODO::v8::Exception
81 class QV8DebugServicePrivate : public QDeclarativeDebugServicePrivate
84 QV8DebugServicePrivate()
86 , scheduleBreak(false)
87 , debuggerThreadIsolate(0)
88 , debuggerThreadEngine(0)
91 //Create an isolate & engine in GUI thread
92 guiThreadIsolate = v8::Isolate::New();
93 v8::Isolate::Scope i_scope(guiThreadIsolate);
95 guiThreadEngine = new QJSEngine();
98 ~QV8DebugServicePrivate()
100 delete debuggerThreadEngine;
101 if (debuggerThreadIsolate)
102 debuggerThreadIsolate->Dispose();
103 delete guiThreadEngine;
104 if (guiThreadIsolate)
105 guiThreadIsolate->Dispose();
108 void sendDebugMessage(const QString &message);
109 static QByteArray packMessage(const QString &message);
114 v8::Isolate *debuggerThreadIsolate;
115 QJSEngine *debuggerThreadEngine;
116 v8::Isolate *guiThreadIsolate;
117 QJSEngine *guiThreadEngine;
119 QList<QDeclarativeEngine *> engines;
121 QHash<QString, QString> sourcePath;
122 QHash<QString, QString> requestCache;
123 QHash<int, SignalHandlerData> handlersList;
126 QV8DebugService::QV8DebugService(QObject *parent)
127 : QDeclarativeDebugService(*(new QV8DebugServicePrivate()),
128 QLatin1String("V8Debugger"), parent)
130 Q_D(QV8DebugService);
131 v8::Debug::SetMessageHandler2(DebugMessageHandler);
133 // This call forces the debugger context to be loaded and made resident.
134 // Without this the debugger is loaded/unloaded whenever required, which
135 // has a very significant effect on the timing reported in the QML
136 // profiler in Qt Creator.
137 v8::Debug::GetDebugContext();
139 if (status() == Enabled) {
140 // ,block mode, client attached
141 while (!d->initialized) {
147 QV8DebugService::~QV8DebugService()
151 QV8DebugService *QV8DebugService::instance()
153 return v8ServiceInstance();
156 void QV8DebugService::addEngine(QDeclarativeEngine *engine)
158 Q_D(QV8DebugService);
160 Q_ASSERT(!d->engines.contains(engine));
162 d->engines.append(engine);
165 void QV8DebugService::removeEngine(QDeclarativeEngine *engine)
167 Q_D(QV8DebugService);
169 Q_ASSERT(d->engines.contains(engine));
171 d->engines.removeAll(engine);
174 void QV8DebugService::debugMessageHandler(const QString &message, bool willStartRunning)
176 Q_D(QV8DebugService);
177 d->isRunning = willStartRunning;
179 if (d->scheduleBreak)
180 scheduledDebugBreak();
182 sendMessage(QV8DebugServicePrivate::packMessage(message));
186 void QV8DebugService::appendSourcePath(const QString &message)
188 Q_D(QV8DebugService);
191 /* Parse the byte string in a separate isolate
192 This will ensure that the debug message handler does not
193 receive any messages related to this operation */
195 v8::Isolate::Scope scope(d->guiThreadIsolate);
196 QJSValue parser = d->guiThreadEngine->evaluate(QLatin1String("JSON.parse"));
197 QJSValue out = parser.call(QJSValue(), QJSValueList() << QJSValue(message));
198 msgMap = out.toVariant().toMap();
201 const QString sourcePath(msgMap.value(QLatin1String("body")).toMap().value(
202 QLatin1String("script")).toMap().value(
203 QLatin1String("name")).toString());
204 const QString fileName(QFileInfo(sourcePath).fileName());
206 d->sourcePath.insert(fileName, sourcePath);
208 //Check if there are any pending breakpoint requests for this file
209 if (d->requestCache.contains(fileName)) {
210 QList<QString> list = d->requestCache.values(fileName);
211 d->requestCache.remove(fileName);
212 foreach (QString request, list) {
213 request.replace(fileName, sourcePath);
214 d->sendDebugMessage(request);
219 void QV8DebugService::signalEmitted(const QString &signal)
221 //This function is only called by QDeclarativeBoundSignal
222 //only if there is a slot connected to the signal. Hence, there
223 //is no need for additional check.
224 Q_D(QV8DebugService);
226 //Parse just the name and remove the class info
227 //Normalize to Lower case.
228 QString signalName = signal.left(signal.indexOf(QLatin1String("("))).toLower();
229 foreach (const SignalHandlerData &data, d->handlersList) {
230 if (data.functionName == signalName
232 d->scheduleBreak = true;
235 if (d->scheduleBreak)
236 scheduledDebugBreak();
239 void QV8DebugService::scheduledDebugBreak()
241 Q_D(QV8DebugService);
242 if (d->scheduleBreak) {
243 v8::Debug::DebugBreak();
244 d->scheduleBreak = false;
248 void QV8DebugService::messageReceived(const QByteArray &message)
250 Q_D(QV8DebugService);
252 QDataStream ds(message);
256 if (command == "V8DEBUG") {
258 if (!d->debuggerThreadEngine) {
259 //Create an isolate & engine in debugger thread
260 d->debuggerThreadIsolate = v8::Isolate::New();
261 v8::Isolate::Scope i_scope(d->debuggerThreadIsolate);
262 v8::V8::Initialize();
263 d->debuggerThreadEngine = new QJSEngine();
269 QByteArray requestArray;
271 request = QString::fromUtf8(requestArray);
276 v8::Isolate::Scope i_scope(d->debuggerThreadIsolate);
277 QJSValue parser = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.parse"));
278 QJSValue out = parser.call(QJSValue(), QJSValueList() << QJSValue(request));
279 reqMap = out.toVariant().toMap();
282 const QString debugCommand(reqMap.value(QLatin1String("command")).toString());
284 if (debugCommand == QLatin1String("connect")) {
285 d->initialized = true;
286 //Prepare the response string
287 //Create a json message using v8 debugging protocol
288 //and send it to client
290 // { "type" : "response",
291 // "request_seq" : <number>,
292 // "command" : "connect",
293 // "running" : <is the VM running after sending this response>
297 const QString obj(QLatin1String("{}"));
298 v8::Isolate::Scope i_scope(d->debuggerThreadIsolate);
299 QJSValue parser = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.parse"));
300 QJSValue jsonVal = parser.call(QJSValue(), QJSValueList() << obj);
301 jsonVal.setProperty(QLatin1String("type"), QJSValue(QLatin1String("response")));
303 const int sequence = reqMap.value(QLatin1String("seq")).toInt();
304 jsonVal.setProperty(QLatin1String("request_seq"), QJSValue(sequence));
305 jsonVal.setProperty(QLatin1String("command"), QJSValue(debugCommand));
306 jsonVal.setProperty(QLatin1String("success"), QJSValue(true));
307 jsonVal.setProperty(QLatin1String("running"), QJSValue(d->isRunning));
309 QJSValue stringify = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.stringify"));
310 QJSValue json = stringify.call(QJSValue(), QJSValueList() << jsonVal);
311 sendMessage(QV8DebugServicePrivate::packMessage(json.toString()));
314 } else if (debugCommand == QLatin1String("interrupt")) {
315 //Prepare the response string
316 //Create a json message using v8 debugging protocol
317 //and send it to client
319 // { "type" : "response",
320 // "request_seq" : <number>,
321 // "command" : "connect",
322 // "running" : <is the VM running after sending this response>
326 const QString obj(QLatin1String("{}"));
327 v8::Isolate::Scope i_scope(d->debuggerThreadIsolate);
328 QJSValue parser = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.parse"));
329 QJSValue jsonVal = parser.call(QJSValue(), QJSValueList() << obj);
330 jsonVal.setProperty(QLatin1String("type"), QJSValue(QLatin1String("response")));
332 const int sequence = reqMap.value(QLatin1String("seq")).toInt();
333 jsonVal.setProperty(QLatin1String("request_seq"), QJSValue(sequence));
334 jsonVal.setProperty(QLatin1String("command"), QJSValue(debugCommand));
335 jsonVal.setProperty(QLatin1String("success"), QJSValue(true));
336 jsonVal.setProperty(QLatin1String("running"), QJSValue(d->isRunning));
338 QJSValue stringify = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.stringify"));
339 QJSValue json = stringify.call(QJSValue(), QJSValueList() << jsonVal);
340 sendMessage(QV8DebugServicePrivate::packMessage(json.toString()));
342 // break has to be executed in gui thread
343 d->scheduleBreak = true;
344 QMetaObject::invokeMethod(this, "scheduledDebugBreak", Qt::QueuedConnection);
346 bool forwardRequestToV8 = true;
348 if (debugCommand == QLatin1String("setbreakpoint")) {
349 const QVariantMap arguments = reqMap.value(QLatin1String("arguments")).toMap();
350 const QString type(arguments.value(QLatin1String("type")).toString());
352 if (type == QLatin1String("script")) {
353 QString fileName(arguments.value(QLatin1String("target")).toString());
355 //Check if the filepath has been cached
356 if (d->sourcePath.contains(fileName)) {
357 QString filePath = d->sourcePath.value(fileName);
358 request.replace(fileName, filePath);
360 //Store the setbreakpoint message till filepath is resolved
361 d->requestCache.insertMulti(fileName, request);
362 forwardRequestToV8 = false;
364 } else if (type == QLatin1String("event")) {
365 //Do not send this request to v8
366 forwardRequestToV8 = false;
368 //Prepare the response string
369 //Create a json message using v8 debugging protocol
370 //and send it to client
372 // { "seq" : <number>,
373 // "type" : "response",
374 // "request_seq" : <number>,
375 // "command" : "setbreakpoint",
376 // "body" : { "type" : <"function" or "script">
377 // "breakpoint" : <break point number of the new break point>
379 // "running" : <is the VM running after sending this response>
383 const QString obj(QLatin1String("{}"));
384 v8::Isolate::Scope i_scope(d->debuggerThreadIsolate);
385 QJSValue parser = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.parse"));
386 QJSValue jsonVal = parser.call(QJSValue(), QJSValueList() << obj);
387 jsonVal.setProperty(QLatin1String("type"), QJSValue(QLatin1String("response")));
389 const int sequence = reqMap.value(QLatin1String("seq")).toInt();
390 jsonVal.setProperty(QLatin1String("request_seq"), QJSValue(sequence));
391 jsonVal.setProperty(QLatin1String("command"), QJSValue(debugCommand));
393 //Check that the function starts with 'on'
394 QString eventName(arguments.value(QLatin1String("target")).toString());
397 if (eventName.startsWith(QLatin1String("on"))) {
398 SignalHandlerData data;
399 //Only store the probable signal name.
400 //Normalize to lower case.
401 data.functionName = eventName.remove(0,2).toLower();
402 data.enabled = arguments.value(QLatin1String("enabled")).toBool();
403 d->handlersList.insert(-sequence, data);
405 QJSValue args = parser.call(QJSValue(), QJSValueList() << obj);
407 args.setProperty(QLatin1String("type"), QJSValue(QLatin1String("event")));
408 args.setProperty(QLatin1String("breakpoint"), QJSValue(-sequence));
410 jsonVal.setProperty(QLatin1String("body"), args);
411 jsonVal.setProperty(QLatin1String("success"), QJSValue(true));
414 jsonVal.setProperty(QLatin1String("success"), QJSValue(false));
418 jsonVal.setProperty(QLatin1String("running"), QJSValue(d->isRunning));
420 QJSValue stringify = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.stringify"));
421 QJSValue json = stringify.call(QJSValue(), QJSValueList() << jsonVal);
422 sendMessage(QV8DebugServicePrivate::packMessage(json.toString()));
425 } else if (debugCommand == QLatin1String("changebreakpoint")) {
426 //check if the breakpoint is a negative integer (event breakpoint)
427 const QVariantMap arguments = reqMap.value(QLatin1String("arguments")).toMap();
428 const int bp = arguments.value(QLatin1String("breakpoint")).toInt();
431 SignalHandlerData data = d->handlersList.value(bp);
432 data.enabled = arguments.value(QLatin1String("enabled")).toBool();
433 d->handlersList.insert(bp, data);
434 forwardRequestToV8 = false;
436 } else if (debugCommand == QLatin1String("clearbreakpoint")) {
437 //check if the breakpoint is a negative integer (event breakpoint)
438 const QVariantMap arguments = reqMap.value(QLatin1String("arguments")).toMap();
439 const int bp = arguments.value(QLatin1String("breakpoint")).toInt();
442 d->handlersList.remove(bp);
443 forwardRequestToV8 = false;
445 } else if (debugCommand == QLatin1String("disconnect")) {
446 v8::Debug::CancelDebugBreak();
449 if (forwardRequestToV8)
450 d->sendDebugMessage(request);
454 QDeclarativeDebugService::messageReceived(message);
457 void QV8DebugServicePrivate::sendDebugMessage(const QString &message)
459 v8::Debug::SendCommand(message.utf16(), message.size());
462 QByteArray QV8DebugServicePrivate::packMessage(const QString &message)
465 QDataStream rs(&reply, QIODevice::WriteOnly);
466 QByteArray cmd("V8DEBUG");
467 rs << cmd << message.toUtf8();