Debugger: Move server into it's own thread
[profile/ivi/qtdeclarative.git] / src / declarative / debugger / qv8debugservice.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 **
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.
17 **
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.
21 **
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.
29 **
30 ** Other Usage
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.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
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>
47
48 #include <QtCore/QHash>
49 #include <QtCore/QFileInfo>
50
51 QT_BEGIN_NAMESPACE
52
53 struct SignalHandlerData
54 {
55     QString functionName;
56     bool enabled;
57 };
58
59 Q_GLOBAL_STATIC(QV8DebugService, v8ServiceInstance)
60
61 void DebugMessageHandler(const v8::Debug::Message& message)
62 {
63     v8::DebugEvent event = message.GetEvent();
64
65     if (event != v8::Break && event != v8::Exception &&
66             event != v8::AfterCompile && event != v8::BeforeCompile) {
67         return;
68     }
69
70     const QString response(QV8Engine::toStringStatic(
71                                   message.GetJSON()));
72
73     QV8DebugService *service = QV8DebugService::instance();
74     service->debugMessageHandler(response, message.WillStartRunning());
75
76     if (event == v8::AfterCompile) {
77         service->appendSourcePath(response);
78     } //TODO::v8::Exception
79 }
80
81 class QV8DebugServicePrivate : public QDeclarativeDebugServicePrivate
82 {
83 public:
84     QV8DebugServicePrivate()
85         :initialized(false)
86         , scheduleBreak(false)
87         , debuggerThreadIsolate(0)
88         , debuggerThreadEngine(0)
89         , isRunning(true)
90     {
91         //Create an isolate & engine in GUI thread
92         guiThreadIsolate = v8::Isolate::New();
93         v8::Isolate::Scope i_scope(guiThreadIsolate);
94         v8::V8::Initialize();
95         guiThreadEngine = new QJSEngine();
96     }
97
98     ~QV8DebugServicePrivate()
99     {
100         delete debuggerThreadEngine;
101         if (debuggerThreadIsolate)
102             debuggerThreadIsolate->Dispose();
103         delete guiThreadEngine;
104         if (guiThreadIsolate)
105             guiThreadIsolate->Dispose();
106     }
107
108     void sendDebugMessage(const QString &message);
109     static QByteArray packMessage(const QString &message);
110
111     bool initialized;
112     bool scheduleBreak;
113
114     v8::Isolate *debuggerThreadIsolate;
115     QJSEngine *debuggerThreadEngine;
116     v8::Isolate *guiThreadIsolate;
117     QJSEngine *guiThreadEngine;
118
119     QList<QDeclarativeEngine *> engines;
120     bool isRunning;
121     QHash<QString, QString> sourcePath;
122     QHash<QString, QString> requestCache;
123     QHash<int, SignalHandlerData> handlersList;
124 };
125
126 QV8DebugService::QV8DebugService(QObject *parent)
127     : QDeclarativeDebugService(*(new QV8DebugServicePrivate()),
128                                QLatin1String("V8Debugger"), parent)
129 {
130     Q_D(QV8DebugService);
131     v8::Debug::SetMessageHandler2(DebugMessageHandler);
132
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();
138
139     if (status() == Enabled) {
140         // ,block mode, client attached
141         while (!d->initialized) {
142             waitForMessage();
143         }
144     }
145 }
146
147 QV8DebugService::~QV8DebugService()
148 {
149 }
150
151 QV8DebugService *QV8DebugService::instance()
152 {
153     return v8ServiceInstance();
154 }
155
156 void QV8DebugService::addEngine(QDeclarativeEngine *engine)
157 {
158     Q_D(QV8DebugService);
159     Q_ASSERT(engine);
160     Q_ASSERT(!d->engines.contains(engine));
161
162     d->engines.append(engine);
163 }
164
165 void QV8DebugService::removeEngine(QDeclarativeEngine *engine)
166 {
167     Q_D(QV8DebugService);
168     Q_ASSERT(engine);
169     Q_ASSERT(d->engines.contains(engine));
170
171     d->engines.removeAll(engine);
172 }
173
174 void QV8DebugService::debugMessageHandler(const QString &message, bool willStartRunning)
175 {
176     Q_D(QV8DebugService);
177     d->isRunning = willStartRunning;
178
179     if (d->scheduleBreak)
180         scheduledDebugBreak();
181
182     sendMessage(QV8DebugServicePrivate::packMessage(message));
183 }
184
185
186 void QV8DebugService::appendSourcePath(const QString &message)
187 {
188     Q_D(QV8DebugService);
189
190     QVariantMap msgMap;
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 */
194     {
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();
199     }
200
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());
205
206     d->sourcePath.insert(fileName, sourcePath);
207
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);
215         }
216     }
217 }
218
219 void QV8DebugService::signalEmitted(const QString &signal)
220 {
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);
225
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
231                 && data.enabled) {
232             d->scheduleBreak = true;
233         }
234     }
235     if (d->scheduleBreak)
236         scheduledDebugBreak();
237 }
238
239 void QV8DebugService::scheduledDebugBreak()
240 {
241     Q_D(QV8DebugService);
242     if (d->scheduleBreak) {
243         v8::Debug::DebugBreak();
244         d->scheduleBreak = false;
245     }
246 }
247
248 void QV8DebugService::messageReceived(const QByteArray &message)
249 {
250     Q_D(QV8DebugService);
251
252     QDataStream ds(message);
253     QByteArray command;
254     ds >> command;
255
256     if (command == "V8DEBUG") {
257
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();
264         }
265
266
267         QString request;
268         {
269             QByteArray requestArray;
270             ds >> requestArray;
271             request = QString::fromUtf8(requestArray);
272         }
273
274         QVariantMap reqMap;
275         {
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();
280         }
281
282         const QString debugCommand(reqMap.value(QLatin1String("command")).toString());
283
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
289
290             // { "type"        : "response",
291             //   "request_seq" : <number>,
292             //   "command"     : "connect",
293             //   "running"     : <is the VM running after sending this response>
294             //   "success"     : true
295             // }
296             {
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")));
302
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));
308
309                 QJSValue stringify = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.stringify"));
310                 QJSValue json = stringify.call(QJSValue(), QJSValueList() << jsonVal);
311                 sendMessage(QV8DebugServicePrivate::packMessage(json.toString()));
312             }
313
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
318
319             // { "type"        : "response",
320             //   "request_seq" : <number>,
321             //   "command"     : "connect",
322             //   "running"     : <is the VM running after sending this response>
323             //   "success"     : true
324             // }
325             {
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")));
331
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));
337
338                 QJSValue stringify = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.stringify"));
339                 QJSValue json = stringify.call(QJSValue(), QJSValueList() << jsonVal);
340                 sendMessage(QV8DebugServicePrivate::packMessage(json.toString()));
341             }
342             // break has to be executed in gui thread
343             d->scheduleBreak = true;
344             QMetaObject::invokeMethod(this, "scheduledDebugBreak", Qt::QueuedConnection);
345         } else {
346             bool forwardRequestToV8 = true;
347
348             if (debugCommand == QLatin1String("setbreakpoint")) {
349                 const QVariantMap arguments = reqMap.value(QLatin1String("arguments")).toMap();
350                 const QString type(arguments.value(QLatin1String("type")).toString());
351
352                 if (type == QLatin1String("script")) {
353                     QString fileName(arguments.value(QLatin1String("target")).toString());
354
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);
359                     } else {
360                         //Store the setbreakpoint message till filepath is resolved
361                         d->requestCache.insertMulti(fileName, request);
362                         forwardRequestToV8 = false;
363                     }
364                 } else if (type == QLatin1String("event")) {
365                     //Do not send this request to v8
366                     forwardRequestToV8 = false;
367
368                     //Prepare the response string
369                     //Create a json message using v8 debugging protocol
370                     //and send it to client
371
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>
378                     //                   }
379                     //   "running"     : <is the VM running after sending this response>
380                     //   "success"     : true
381                     // }
382                     {
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")));
388
389                         const int sequence = reqMap.value(QLatin1String("seq")).toInt();
390                         jsonVal.setProperty(QLatin1String("request_seq"), QJSValue(sequence));
391                         jsonVal.setProperty(QLatin1String("command"), QJSValue(debugCommand));
392
393                         //Check that the function starts with 'on'
394                         QString eventName(arguments.value(QLatin1String("target")).toString());
395
396
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);
404
405                             QJSValue args = parser.call(QJSValue(), QJSValueList() << obj);
406
407                             args.setProperty(QLatin1String("type"), QJSValue(QLatin1String("event")));
408                             args.setProperty(QLatin1String("breakpoint"), QJSValue(-sequence));
409
410                             jsonVal.setProperty(QLatin1String("body"), args);
411                             jsonVal.setProperty(QLatin1String("success"), QJSValue(true));
412
413                         } else {
414                             jsonVal.setProperty(QLatin1String("success"), QJSValue(false));
415                         }
416
417
418                         jsonVal.setProperty(QLatin1String("running"), QJSValue(d->isRunning));
419
420                         QJSValue stringify = d->debuggerThreadEngine->evaluate(QLatin1String("JSON.stringify"));
421                         QJSValue json = stringify.call(QJSValue(), QJSValueList() << jsonVal);
422                         sendMessage(QV8DebugServicePrivate::packMessage(json.toString()));
423                     }
424                 }
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();
429
430                 if (bp < 0) {
431                     SignalHandlerData data = d->handlersList.value(bp);
432                     data.enabled = arguments.value(QLatin1String("enabled")).toBool();
433                     d->handlersList.insert(bp, data);
434                     forwardRequestToV8 = false;
435                 }
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();
440
441                 if (bp < 0) {
442                     d->handlersList.remove(bp);
443                     forwardRequestToV8 = false;
444                 }
445             } else if (debugCommand == QLatin1String("disconnect")) {
446                 v8::Debug::CancelDebugBreak();
447             }
448
449             if (forwardRequestToV8)
450                 d->sendDebugMessage(request);
451         }
452     }
453
454     QDeclarativeDebugService::messageReceived(message);
455 }
456
457 void QV8DebugServicePrivate::sendDebugMessage(const QString &message)
458 {
459     v8::Debug::SendCommand(message.utf16(), message.size());
460 }
461
462 QByteArray QV8DebugServicePrivate::packMessage(const QString &message)
463 {
464     QByteArray reply;
465     QDataStream rs(&reply, QIODevice::WriteOnly);
466     QByteArray cmd("V8DEBUG");
467     rs << cmd << message.toUtf8();
468     return reply;
469 }
470
471 QT_END_NAMESPACE