QV8DebugService: QML Signal Handling
[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/QEventLoop>
49 #include <QtCore/QHash>
50 #include <QtCore/QFileInfo>
51
52 QT_BEGIN_NAMESPACE
53
54 struct SignalHandlerData
55 {
56     QString functionName;
57     bool enabled;
58 };
59
60 Q_GLOBAL_STATIC(QV8DebugService, v8ServiceInstance)
61
62 void DebugMessageHandler(const v8::Debug::Message& message)
63 {
64     v8::DebugEvent event = message.GetEvent();
65
66     if (event != v8::Break && event != v8::Exception &&
67             event != v8::AfterCompile && event != v8::BeforeCompile) {
68         return;
69     }
70
71     const QString response(QV8Engine::toStringStatic(
72                                   message.GetJSON()));
73
74     QV8DebugService *service = QV8DebugService::instance();
75     service->debugMessageHandler(response);
76
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
83 }
84
85 class QV8DebugServicePrivate : public QDeclarativeDebugServicePrivate
86 {
87 public:
88     QV8DebugServicePrivate()
89         :initialized(false)
90     {
91         //Create a new isolate
92         isolate = v8::Isolate::New();
93
94         //Enter the isolate and initialize
95         v8::Isolate::Scope i_scope(isolate);
96         v8::V8::Initialize();
97
98         //Create an instance in the new isolate
99         engine = new QJSEngine();
100     }
101
102     ~QV8DebugServicePrivate()
103     {
104         delete engine;
105         isolate->Dispose();
106     }
107
108     void sendDebugMessage(const QString &message);
109     static QByteArray packMessage(const QString &message);
110
111     bool initialized;
112     QJSEngine *engine;
113     v8::Isolate *isolate;
114     QList<QDeclarativeEngine *> engines;
115     QEventLoop loop;
116     QHash<QString, QString> sourcePath;
117     QHash<QString, QString> requestCache;
118     QHash<int, SignalHandlerData> handlersList;
119 };
120
121 QV8DebugService::QV8DebugService(QObject *parent)
122     : QDeclarativeDebugService(*(new QV8DebugServicePrivate()),
123                                QLatin1String("V8Debugger"), parent)
124 {
125     Q_D(QV8DebugService);
126     v8::Debug::SetMessageHandler2(DebugMessageHandler);
127     if (status() == Enabled) {
128         // ,block mode, client attached
129         while (!d->initialized) {
130             waitForMessage();
131         }
132     }
133 }
134
135 QV8DebugService::~QV8DebugService()
136 {
137 }
138
139 QV8DebugService *QV8DebugService::instance()
140 {
141     return v8ServiceInstance();
142 }
143
144 void QV8DebugService::addEngine(QDeclarativeEngine *engine)
145 {
146     Q_D(QV8DebugService);
147     Q_ASSERT(engine);
148     Q_ASSERT(!d->engines.contains(engine));
149
150     d->engines.append(engine);
151 }
152
153 void QV8DebugService::removeEngine(QDeclarativeEngine *engine)
154 {
155     Q_D(QV8DebugService);
156     Q_ASSERT(engine);
157     Q_ASSERT(d->engines.contains(engine));
158
159     d->engines.removeAll(engine);
160 }
161
162 void QV8DebugService::debugMessageHandler(const QString &message)
163 {
164     sendMessage(QV8DebugServicePrivate::packMessage(message));
165 }
166
167 void QV8DebugService::executionStopped()
168 {
169     Q_D(QV8DebugService);
170
171     if (!d->loop.isRunning()) {
172         d->loop.exec(QEventLoop::ExcludeUserInputEvents);
173     }
174 }
175
176 void QV8DebugService::appendSourcePath(const QString &message)
177 {
178     Q_D(QV8DebugService);
179
180     QVariantMap msgMap;
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 */
184     {
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();
189     }
190
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());
195
196     d->sourcePath.insert(fileName, sourcePath);
197
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);
205         }
206     }
207 }
208
209 void QV8DebugService::signalEmitted(const QString &signal)
210 {
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);
215
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
222                 && data.enabled) {
223             debugBreak = true;
224         }
225     }
226     if (debugBreak)
227         v8::Debug::DebugBreak();
228 }
229
230 void QV8DebugService::messageReceived(const QByteArray &message)
231 {
232     Q_D(QV8DebugService);
233
234     QDataStream ds(message);
235     QByteArray command;
236     ds >> command;
237
238     if (command == "V8DEBUG") {
239         QString request;
240         {
241             QByteArray requestArray;
242             ds >> requestArray;
243             request = QString::fromUtf8(requestArray);
244         }
245
246         QVariantMap reqMap;
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 */
250         {
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();
255         }
256
257         const QString debugCommand(reqMap.value(QLatin1String("command")).toString());
258
259         if (debugCommand == QLatin1String("connect")) {
260             d->initialized = true;
261
262         } else if (debugCommand == QLatin1String("interrupt")) {
263             v8::Debug::DebugBreak();
264
265         } else {
266             bool forwardRequestToV8 = true;
267
268             if (debugCommand == QLatin1String("setbreakpoint")) {
269                 const QVariantMap arguments = reqMap.value(QLatin1String("arguments")).toMap();
270                 const QString type(arguments.value(QLatin1String("type")).toString());
271
272                 if (type == QLatin1String("script")) {
273                     QString fileName(arguments.value(QLatin1String("target")).toString());
274
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);
279                     } else {
280                         //Store the setbreakpoint message till filepath is resolved
281                         d->requestCache.insertMulti(fileName, request);
282                         forwardRequestToV8 = false;
283                     }
284                 } else if (type == QLatin1String("event")) {
285                     //Do not send this request to v8
286                     forwardRequestToV8 = false;
287
288                     //Prepare the response string
289                     //Create a json message using v8 debugging protocol
290                     //and send it to client
291
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>
298                     //                   }
299                     //   "running"     : <is the VM running after sending this response>
300                     //   "success"     : true
301                     // }
302                     {
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")));
308
309                         const int sequence = reqMap.value(QLatin1String("seq")).toInt();
310                         jsonVal.setProperty(QLatin1String("request_seq"), QJSValue(sequence));
311                         jsonVal.setProperty(QLatin1String("command"), QJSValue(debugCommand));
312
313                         //Check that the function starts with 'on'
314                         QString eventName(arguments.value(QLatin1String("target")).toString());
315
316
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);
324
325                             QJSValue args = parser.call(QJSValue(), QJSValueList() << obj);
326
327                             args.setProperty(QLatin1String("type"), QJSValue(QLatin1String("event")));
328                             args.setProperty(QLatin1String("breakpoint"), QJSValue(-sequence));
329
330                             jsonVal.setProperty(QLatin1String("body"), args);
331                             jsonVal.setProperty(QLatin1String("success"), QJSValue(true));
332
333                         } else {
334                             jsonVal.setProperty(QLatin1String("success"), QJSValue(false));
335                         }
336
337
338                         jsonVal.setProperty(QLatin1String("running"), QJSValue(!d->loop.isRunning()));
339
340                         QJSValue stringify = d->engine->evaluate(QLatin1String("JSON.stringify"));
341                         QJSValue json = stringify.call(QJSValue(), QJSValueList() << jsonVal);
342                         debugMessageHandler(json.toString());
343
344                     }
345                 }
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();
350
351                 if (bp < 0) {
352                     SignalHandlerData data = d->handlersList.value(bp);
353                     data.enabled = arguments.value(QLatin1String("enabled")).toBool();
354                     d->handlersList.insert(bp, data);
355                     forwardRequestToV8 = false;
356                 }
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();
361
362                 if (bp < 0) {
363                     d->handlersList.remove(bp);
364                     forwardRequestToV8 = false;
365                 }
366             }
367             if (forwardRequestToV8)
368                 d->sendDebugMessage(request);
369         }
370     }
371
372     QDeclarativeDebugService::messageReceived(message);
373 }
374
375 void QV8DebugServicePrivate::sendDebugMessage(const QString &message)
376 {
377     if (loop.isRunning())
378         loop.exit();
379
380     v8::Debug::SendCommand(message.utf16(), message.size());
381 }
382
383 QByteArray QV8DebugServicePrivate::packMessage(const QString &message)
384 {
385     QByteArray reply;
386     QDataStream rs(&reply, QIODevice::WriteOnly);
387     QByteArray cmd("V8DEBUG");
388     rs << cmd << message.toUtf8();
389     return reply;
390 }
391
392 QT_END_NAMESPACE