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 // DebugMessageHandler will call back already when the QV8DebugService constructor is
62 // running, we therefore need a plain pointer.
63 static QV8DebugService *v8ServiceInstancePtr = 0;
65 void DebugMessageHandler(const v8::Debug::Message& message)
67 v8::DebugEvent event = message.GetEvent();
69 if (event != v8::Break && event != v8::Exception &&
70 event != v8::AfterCompile && event != v8::BeforeCompile) {
74 const QString response(QV8Engine::toStringStatic(
77 v8ServiceInstancePtr->debugMessageHandler(response, message.WillStartRunning());
79 if (event == v8::AfterCompile) {
80 v8ServiceInstancePtr->appendSourcePath(response);
81 } //TODO::v8::Exception
84 class QV8DebugServicePrivate : public QDeclarativeDebugServicePrivate
87 QV8DebugServicePrivate()
88 : connectReceived(false)
89 , scheduleBreak(false)
90 , debuggerThreadIsolate(0)
91 , debuggerThreadEngine(0)
99 ~QV8DebugServicePrivate()
101 delete debuggerThreadEngine;
102 if (debuggerThreadIsolate)
103 debuggerThreadIsolate->Dispose();
104 delete guiThreadEngine;
105 if (guiThreadIsolate)
106 guiThreadIsolate->Dispose();
109 void initializeDebuggerThread();
111 void sendDebugMessage(const QString &message);
112 void updateSourcePath(const QString &sourcePath);
113 static QByteArray packMessage(const QString &message);
115 bool connectReceived;
118 v8::Isolate *debuggerThreadIsolate;
119 QJSEngine *debuggerThreadEngine;
120 v8::Isolate *guiThreadIsolate;
121 QJSEngine *guiThreadEngine;
122 // keep messageReceived() from running until initialize() has finished
123 QMutex initializeMutex;
125 QList<QDeclarativeEngine *> engines;
127 QHash<QString, QString> sourcePath;
128 QHash<QString, QString> requestCache;
129 QHash<int, SignalHandlerData> handlersList;
130 int internalRequests;
133 QV8DebugService::QV8DebugService(QObject *parent)
134 : QDeclarativeDebugService(*(new QV8DebugServicePrivate()),
135 QLatin1String("V8Debugger"), parent)
137 Q_D(QV8DebugService);
138 v8ServiceInstancePtr = this;
139 // wait for statusChanged() -> initialize()
140 d->initializeMutex.lock();
141 if (registerService() == Enabled) {
143 // ,block mode, client attached
144 while (!d->connectReceived) {
148 d->initializeMutex.unlock();
152 QV8DebugService::~QV8DebugService()
156 QV8DebugService *QV8DebugService::instance()
158 return v8ServiceInstance();
161 void QV8DebugService::addEngine(QDeclarativeEngine *engine)
163 Q_D(QV8DebugService);
165 Q_ASSERT(!d->engines.contains(engine));
167 d->engines.append(engine);
170 void QV8DebugService::removeEngine(QDeclarativeEngine *engine)
172 Q_D(QV8DebugService);
174 Q_ASSERT(d->engines.contains(engine));
176 d->engines.removeAll(engine);
179 void QV8DebugService::debugMessageHandler(const QString &message, bool willStartRunning)
181 Q_D(QV8DebugService);
182 d->isRunning = willStartRunning;
184 if (d->scheduleBreak)
185 scheduledDebugBreak();
187 if (d->internalRequests > 0) {
188 // there are outstanding internal requests,
189 // check the sequence number: internal if seq is -1
191 QVariantMap responseMap;
193 v8::Isolate::Scope i_scope(d->guiThreadIsolate);
194 QJSValue parser = d->guiThreadEngine->evaluate(QLatin1String("JSON.parse"));
195 QJSValue out = parser.call(QJSValue(), QJSValueList() << QJSValue(message));
196 responseMap = out.toVariant().toMap();
199 if (responseMap.value(QLatin1String("request_seq")).toInt() == -1) {
200 if (responseMap.value(QLatin1String("command")) == QLatin1String("scripts")) {
202 // Reply to scripts request on connect:
205 // "type": "response",
206 // "request_seq": <number>,
207 // "command": "scripts",
208 // "body": [ { "name" : <name of script> } ]
211 QVariantList body = responseMap.value(QLatin1String("body")).toList();
212 foreach (const QVariant &listEntry, body) {
213 QVariantMap entryMap = listEntry.toMap();
214 const QString sourcePath = entryMap.value(QLatin1String("name")).toString();
215 d->updateSourcePath(sourcePath);
219 d->internalRequests--;
223 sendMessage(QV8DebugServicePrivate::packMessage(message));
227 void QV8DebugService::appendSourcePath(const QString &message)
229 Q_D(QV8DebugService);
232 /* Parse the byte string in a separate isolate
233 This will ensure that the debug message handler does not
234 receive any messages related to this operation */
236 v8::Isolate::Scope scope(d->guiThreadIsolate);
237 QJSValue parser = d->guiThreadEngine->evaluate(QLatin1String("JSON.parse"));
238 QJSValue out = parser.call(QJSValue(), QJSValueList() << QJSValue(message));
239 msgMap = out.toVariant().toMap();
242 const QString sourcePath(msgMap.value(QLatin1String("body")).toMap().value(
243 QLatin1String("script")).toMap().value(
244 QLatin1String("name")).toString());
245 d->updateSourcePath(sourcePath);
248 void QV8DebugService::signalEmitted(const QString &signal)
250 //This function is only called by QDeclarativeBoundSignal
251 //only if there is a slot connected to the signal. Hence, there
252 //is no need for additional check.
253 Q_D(QV8DebugService);
255 //Parse just the name and remove the class info
256 //Normalize to Lower case.
257 QString signalName = signal.left(signal.indexOf(QLatin1String("("))).toLower();
258 foreach (const SignalHandlerData &data, d->handlersList) {
259 if (data.functionName == signalName
261 d->scheduleBreak = true;
264 if (d->scheduleBreak)
265 scheduledDebugBreak();
268 void QV8DebugService::initialize()
270 Q_D(QV8DebugService);
271 v8::Debug::SetMessageHandler2(DebugMessageHandler);
273 //Create an isolate & engine for parsing JSON messages in GUI thread
274 d->guiThreadIsolate = v8::Isolate::New();
275 v8::Isolate::Scope scope(d->guiThreadIsolate);
276 d->guiThreadEngine = new QJSEngine();
278 d->initializeMutex.unlock();
281 void QV8DebugService::scheduledDebugBreak()
283 Q_D(QV8DebugService);
284 if (d->scheduleBreak) {
285 v8::Debug::DebugBreak();
286 d->scheduleBreak = false;
290 // executed in the debugger thread
291 void QV8DebugService::statusChanged(QDeclarativeDebugService::Status newStatus)
293 Q_D(QV8DebugService);
294 if (newStatus == Enabled) {
295 if (!d->debuggerThreadEngine)
296 d->initializeDebuggerThread();
298 // execute in GUI thread
299 d->initializeMutex.lock();
300 QMetaObject::invokeMethod(this, "initialize", Qt::QueuedConnection);
302 // Request already compiled scripts from v8 (recycling the sequence number from connect)
305 // "type" : "request",
306 // "command" : "scripts",
307 // "arguments" : { "includeSource" : false }
309 const QString obj(QLatin1String("{}"));
310 v8::Isolate::Scope i_scope(d->debuggerThreadIsolate);
311 QJSValue parser = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.parse"));
312 QJSValue jsonVal = parser.call(QJSValue(), QJSValueList() << obj);
313 jsonVal.setProperty(QLatin1String("type"), QJSValue(QLatin1String("request")));
314 jsonVal.setProperty(QLatin1String("seq"), QJSValue(-1));
315 jsonVal.setProperty(QLatin1String("command"), QJSValue(QLatin1String("scripts")));
317 QJSValue args = parser.call(QJSValue(), QJSValueList() << obj);
319 args.setProperty(QLatin1String("includeSource"), QJSValue(false));
320 jsonVal.setProperty(QLatin1String("arguments"), args);
322 QJSValue stringify = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.stringify"));
323 QJSValue json = stringify.call(QJSValue(), QJSValueList() << jsonVal);
324 d->internalRequests++;
325 d->sendDebugMessage(json.toString());
329 // executed in the debugger thread
330 void QV8DebugService::messageReceived(const QByteArray &message)
332 Q_D(QV8DebugService);
334 QDataStream ds(message);
338 QMutexLocker locker(&d->initializeMutex);
340 if (command == "V8DEBUG") {
341 if (!d->debuggerThreadEngine)
342 d->initializeDebuggerThread();
347 QByteArray requestArray;
349 request = QString::fromUtf8(requestArray);
354 v8::Isolate::Scope i_scope(d->debuggerThreadIsolate);
355 QJSValue parser = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.parse"));
356 QJSValue out = parser.call(QJSValue(), QJSValueList() << QJSValue(request));
357 reqMap = out.toVariant().toMap();
359 const QString debugCommand(reqMap.value(QLatin1String("command")).toString());
360 const int sequence = reqMap.value(QLatin1String("seq")).toInt();
362 if (debugCommand == QLatin1String("connect")) {
363 d->connectReceived = true;
364 //Prepare the response string
365 //Create a json message using v8 debugging protocol
366 //and send it to client
368 // { "type" : "response",
369 // "request_seq" : <number>,
370 // "command" : "connect",
371 // "running" : <is the VM running after sending this response>
375 const QString obj(QLatin1String("{}"));
376 v8::Isolate::Scope i_scope(d->debuggerThreadIsolate);
377 QJSValue parser = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.parse"));
378 QJSValue jsonVal = parser.call(QJSValue(), QJSValueList() << obj);
379 jsonVal.setProperty(QLatin1String("type"), QJSValue(QLatin1String("response")));
381 jsonVal.setProperty(QLatin1String("request_seq"), QJSValue(sequence));
382 jsonVal.setProperty(QLatin1String("command"), QJSValue(debugCommand));
383 jsonVal.setProperty(QLatin1String("success"), QJSValue(true));
384 jsonVal.setProperty(QLatin1String("running"), QJSValue(d->isRunning));
386 QJSValue stringify = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.stringify"));
387 QJSValue json = stringify.call(QJSValue(), QJSValueList() << jsonVal);
388 sendMessage(QV8DebugServicePrivate::packMessage(json.toString()));
390 } else if (debugCommand == QLatin1String("interrupt")) {
391 //Prepare the response string
392 //Create a json message using v8 debugging protocol
393 //and send it to client
395 // { "type" : "response",
396 // "request_seq" : <number>,
397 // "command" : "interrupt",
398 // "running" : <is the VM running after sending this response>
402 const QString obj(QLatin1String("{}"));
403 v8::Isolate::Scope i_scope(d->debuggerThreadIsolate);
404 QJSValue parser = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.parse"));
405 QJSValue jsonVal = parser.call(QJSValue(), QJSValueList() << obj);
406 jsonVal.setProperty(QLatin1String("type"), QJSValue(QLatin1String("response")));
408 const int sequence = reqMap.value(QLatin1String("seq")).toInt();
409 jsonVal.setProperty(QLatin1String("request_seq"), QJSValue(sequence));
410 jsonVal.setProperty(QLatin1String("command"), QJSValue(debugCommand));
411 jsonVal.setProperty(QLatin1String("success"), QJSValue(true));
412 jsonVal.setProperty(QLatin1String("running"), QJSValue(d->isRunning));
414 QJSValue stringify = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.stringify"));
415 QJSValue json = stringify.call(QJSValue(), QJSValueList() << jsonVal);
416 sendMessage(QV8DebugServicePrivate::packMessage(json.toString()));
418 // break has to be executed in gui thread
419 d->scheduleBreak = true;
420 QMetaObject::invokeMethod(this, "scheduledDebugBreak", Qt::QueuedConnection);
422 bool forwardRequestToV8 = true;
424 if (debugCommand == QLatin1String("setbreakpoint")) {
425 const QVariantMap arguments = reqMap.value(QLatin1String("arguments")).toMap();
426 const QString type(arguments.value(QLatin1String("type")).toString());
428 if (type == QLatin1String("script")) {
429 QString fileName(arguments.value(QLatin1String("target")).toString());
431 //Check if the filepath has been cached
432 if (d->sourcePath.contains(fileName)) {
433 QString filePath = d->sourcePath.value(fileName);
434 request.replace(fileName, filePath);
436 //Store the setbreakpoint message till filepath is resolved
437 d->requestCache.insertMulti(fileName, request);
438 forwardRequestToV8 = false;
440 } else if (type == QLatin1String("event")) {
441 //Do not send this request to v8
442 forwardRequestToV8 = false;
444 //Prepare the response string
445 //Create a json message using v8 debugging protocol
446 //and send it to client
448 // { "seq" : <number>,
449 // "type" : "response",
450 // "request_seq" : <number>,
451 // "command" : "setbreakpoint",
452 // "body" : { "type" : <"function" or "script">
453 // "breakpoint" : <break point number of the new break point>
455 // "running" : <is the VM running after sending this response>
459 const QString obj(QLatin1String("{}"));
460 v8::Isolate::Scope i_scope(d->debuggerThreadIsolate);
461 QJSValue parser = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.parse"));
462 QJSValue jsonVal = parser.call(QJSValue(), QJSValueList() << obj);
463 jsonVal.setProperty(QLatin1String("type"), QJSValue(QLatin1String("response")));
465 const int sequence = reqMap.value(QLatin1String("seq")).toInt();
466 jsonVal.setProperty(QLatin1String("request_seq"), QJSValue(sequence));
467 jsonVal.setProperty(QLatin1String("command"), QJSValue(debugCommand));
469 //Check that the function starts with 'on'
470 QString eventName(arguments.value(QLatin1String("target")).toString());
473 if (eventName.startsWith(QLatin1String("on"))) {
474 SignalHandlerData data;
475 //Only store the probable signal name.
476 //Normalize to lower case.
477 data.functionName = eventName.remove(0,2).toLower();
478 data.enabled = arguments.value(QLatin1String("enabled")).toBool();
479 d->handlersList.insert(-sequence, data);
481 QJSValue args = parser.call(QJSValue(), QJSValueList() << obj);
483 args.setProperty(QLatin1String("type"), QJSValue(QLatin1String("event")));
484 args.setProperty(QLatin1String("breakpoint"), QJSValue(-sequence));
486 jsonVal.setProperty(QLatin1String("body"), args);
487 jsonVal.setProperty(QLatin1String("success"), QJSValue(true));
490 jsonVal.setProperty(QLatin1String("success"), QJSValue(false));
494 jsonVal.setProperty(QLatin1String("running"), QJSValue(d->isRunning));
496 QJSValue stringify = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.stringify"));
497 QJSValue json = stringify.call(QJSValue(), QJSValueList() << jsonVal);
498 sendMessage(QV8DebugServicePrivate::packMessage(json.toString()));
501 } else if (debugCommand == QLatin1String("changebreakpoint")) {
502 //check if the breakpoint is a negative integer (event breakpoint)
503 const QVariantMap arguments = reqMap.value(QLatin1String("arguments")).toMap();
504 const int bp = arguments.value(QLatin1String("breakpoint")).toInt();
507 SignalHandlerData data = d->handlersList.value(bp);
508 data.enabled = arguments.value(QLatin1String("enabled")).toBool();
509 d->handlersList.insert(bp, data);
510 forwardRequestToV8 = false;
512 } else if (debugCommand == QLatin1String("clearbreakpoint")) {
513 //check if the breakpoint is a negative integer (event breakpoint)
514 const QVariantMap arguments = reqMap.value(QLatin1String("arguments")).toMap();
515 const int bp = arguments.value(QLatin1String("breakpoint")).toInt();
518 d->handlersList.remove(bp);
519 forwardRequestToV8 = false;
521 } else if (debugCommand == QLatin1String("disconnect")) {
522 v8::Debug::CancelDebugBreak();
525 if (forwardRequestToV8)
526 d->sendDebugMessage(request);
531 void QV8DebugServicePrivate::initializeDebuggerThread()
533 Q_ASSERT(!debuggerThreadEngine);
535 //Create an isolate & engine in debugger thread
536 debuggerThreadIsolate = v8::Isolate::New();
537 v8::Isolate::Scope i_scope(debuggerThreadIsolate);
538 debuggerThreadEngine = new QJSEngine();
541 void QV8DebugServicePrivate::sendDebugMessage(const QString &message)
543 v8::Debug::SendCommand(message.utf16(), message.size());
546 void QV8DebugServicePrivate::updateSourcePath(const QString &source)
548 const QString fileName(QFileInfo(source).fileName());
549 sourcePath.insert(fileName, source);
551 //Check if there are any pending breakpoint requests for this file
552 if (requestCache.contains(fileName)) {
553 QList<QString> list = requestCache.values(fileName);
554 requestCache.remove(fileName);
555 foreach (QString request, list) {
556 request.replace(fileName, source);
557 sendDebugMessage(request);
562 QByteArray QV8DebugServicePrivate::packMessage(const QString &message)
565 QDataStream rs(&reply, QIODevice::WriteOnly);
566 QByteArray cmd("V8DEBUG");
567 rs << cmd << message.toUtf8();