V8Debugger: Code beautification
[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 "qv8debug_p.h"
45 #include "qv8engine_p.h"
46 #include "qdeclarativeengine_p.h"
47
48 #include <QtCore/QEventLoop>
49 #include <QtCore/QHash>
50 #include <QtCore/QFileInfo>
51
52 QT_BEGIN_NAMESPACE
53
54 Q_GLOBAL_STATIC(QV8DebugService, v8ServiceInstance)
55
56 void DebugMessageHandler(const v8::Debug::Message& message)
57 {
58     v8::DebugEvent event = message.GetEvent();
59
60     if (event != v8::Break && event != v8::Exception &&
61             event != v8::AfterCompile && event != v8::BeforeCompile) {
62         return;
63     }
64
65     const QString response(QV8Engine::toStringStatic(
66                                   message.GetJSON()));
67
68     QV8DebugService *service = QV8DebugService::instance();
69     service->debugMessageHandler(response);
70
71     if ((event == v8::Break || event == v8::Exception) &&
72             !message.WillStartRunning()) {
73         service->executionStopped();
74     } else if (event == v8::AfterCompile) {
75         service->appendSourcePath(response);
76     } //TODO::v8::Exception
77 }
78
79 class QV8DebugServicePrivate : public QDeclarativeDebugServicePrivate
80 {
81 public:
82     QV8DebugServicePrivate()
83         :initialized(false)
84     {
85         //Create a new isolate
86         isolate = v8::Isolate::New();
87
88         //Enter the isolate and initialize
89         v8::Isolate::Scope i_scope(isolate);
90         v8::V8::Initialize();
91
92         //Create an instance in the new isolate
93         engine = new QJSEngine();
94     }
95
96     ~QV8DebugServicePrivate()
97     {
98         delete engine;
99         isolate->Dispose();
100     }
101
102     void sendDebugMessage(const QString &message);
103     static QByteArray packMessage(const QString &message);
104
105     bool initialized;
106     QJSEngine *engine;
107     v8::Isolate *isolate;
108     QList<QDeclarativeEngine *> engines;
109     QEventLoop loop;
110     QHash<QString,QString> sourcePath;
111     QHash<QString,QString> requestCache;
112     QHash<int,QString> eventList;
113 };
114
115 QV8DebugService::QV8DebugService(QObject *parent)
116     : QDeclarativeDebugService(*(new QV8DebugServicePrivate()),
117                                QLatin1String("V8Debugger"), parent)
118 {
119     Q_D(QV8DebugService);
120     v8::Debug::SetMessageHandler2(DebugMessageHandler);
121     if (status() == Enabled) {
122         // ,block mode, client attached
123         while (!d->initialized) {
124             waitForMessage();
125         }
126     }
127 }
128
129 QV8DebugService::~QV8DebugService()
130 {
131 }
132
133 QV8DebugService *QV8DebugService::instance()
134 {
135     return v8ServiceInstance();
136 }
137
138 void QV8DebugService::addEngine(QDeclarativeEngine *engine)
139 {
140     Q_D(QV8DebugService);
141     Q_ASSERT(engine);
142     Q_ASSERT(!d->engines.contains(engine));
143
144     d->engines.append(engine);
145 }
146
147 void QV8DebugService::removeEngine(QDeclarativeEngine *engine)
148 {
149     Q_D(QV8DebugService);
150     Q_ASSERT(engine);
151     Q_ASSERT(d->engines.contains(engine));
152
153     d->engines.removeAll(engine);
154 }
155
156 void QV8DebugService::debugMessageHandler(const QString &message)
157 {
158     sendMessage(QV8DebugServicePrivate::packMessage(message));
159 }
160
161 void QV8DebugService::executionStopped()
162 {
163     Q_D(QV8DebugService);
164
165     if (!d->loop.isRunning()) {
166         d->loop.exec(QEventLoop::ExcludeUserInputEvents);
167     }
168 }
169
170 void QV8DebugService::appendSourcePath(const QString &message)
171 {
172     Q_D(QV8DebugService);
173
174     QVariantMap msgMap;
175     /* Parse the byte string in a separate isolate
176     This will ensure that the debug message handler does not
177     receive any messages related to this operation */
178     {
179         v8::Isolate::Scope i_scope(d->isolate);
180         QJSValue parser = d->engine->evaluate(QLatin1String("JSON.parse"));
181         QJSValue out = parser.call(QJSValue(), QJSValueList() << QJSValue(message));
182         msgMap = out.toVariant().toMap();
183     }
184
185     const QString sourcePath(msgMap.value(QLatin1String("body")).toMap().value(
186                                  QLatin1String("script")).toMap().value(
187                                  QLatin1String("name")).toString());
188     const QString fileName(QFileInfo(sourcePath).fileName());
189
190     d->sourcePath.insert(fileName, sourcePath);
191
192     //Check if there are any pending breakpoint requests for this file
193     if (d->requestCache.contains(fileName)) {
194         QList<QString> list = d->requestCache.values(fileName);
195         d->requestCache.remove(fileName);
196         foreach (QString request, list) {
197             request.replace(fileName, sourcePath);
198             d->sendDebugMessage(request);
199         }
200     }
201 }
202
203 void QV8DebugService::signalEmitted(const QString &signal)
204 {
205     //This function is only called by QDeclarativeBoundSignal
206     //only if there is a slot connected to the signal. Hence, there
207     //is no need for additional check.
208     Q_D(QV8DebugService);
209
210     //Parse just the name and remove the class info
211     if (d->eventList.key(signal.left(signal.indexOf(QLatin1String("("))))) {
212         v8::Debug::DebugBreak();
213     }
214 }
215
216 void QV8DebugService::messageReceived(const QByteArray &message)
217 {
218     Q_D(QV8DebugService);
219
220     QDataStream ds(message);
221     QByteArray command;
222     ds >> command;
223
224     if (command == "V8DEBUG") {
225         QString request;
226         {
227             QByteArray requestArray;
228             ds >> requestArray;
229             request = QString::fromUtf8(requestArray);
230         }
231
232         QVariantMap reqMap;
233         /* Parse the byte string in a separate isolate
234         This will ensure that the debug message handler does not
235         receive any messages related to this operation */
236         {
237             v8::Isolate::Scope i_scope(d->isolate);
238             QJSValue parser = d->engine->evaluate(QLatin1String("JSON.parse"));
239             QJSValue out = parser.call(QJSValue(), QJSValueList() << QJSValue(request));
240             reqMap = out.toVariant().toMap();
241         }
242
243         const QString debugCommand(reqMap.value(QLatin1String("command")).toString());
244
245         if (debugCommand == QLatin1String("connect")) {
246             d->initialized = true;
247
248         } else if (debugCommand == QLatin1String("interrupt")) {
249             v8::Debug::DebugBreak();
250
251         } else {
252             bool forwardRequestToV8 = true;
253
254             if (debugCommand == QLatin1String("setbreakpoint")) {
255                 const QVariantMap arguments = reqMap.value(QLatin1String("arguments")).toMap();
256                 const QString type(arguments.value(QLatin1String("type")).toString());
257
258                 if (type == QLatin1String("script")) {
259                     QString fileName(arguments.value(QLatin1String("target")).toString());
260
261                     //Check if the filepath has been cached
262                     if (d->sourcePath.contains(fileName)) {
263                         QString filePath = d->sourcePath.value(fileName);
264                         request.replace(fileName, filePath);
265                     } else {
266                         //Store the setbreakpoint message till filepath is resolved
267                         d->requestCache.insertMulti(fileName, request);
268                         forwardRequestToV8 = false;
269                     }
270                 } else if (type == QLatin1String("event")) {
271                     //Do not send this request to v8
272                     forwardRequestToV8 = false;
273
274                     //Prepare the response string
275                     //Create a json message using v8 debugging protocol
276                     //and send it to client
277
278                     // { "seq"         : <number>,
279                     //   "type"        : "response",
280                     //   "request_seq" : <number>,
281                     //   "command"     : "setbreakpoint",
282                     //   "body"        : { "type"       : <"function" or "script">
283                     //                     "breakpoint" : <break point number of the new break point>
284                     //                   }
285                     //   "running"     : <is the VM running after sending this response>
286                     //   "success"     : true
287                     // }
288                     {
289                         v8::Isolate::Scope(d->isolate);
290                         const QString obj(QLatin1String("{}"));
291                         QJSValue parser = d->engine->evaluate(QLatin1String("JSON.parse"));
292                         QJSValue jsonVal = parser.call(QJSValue(), QJSValueList() << obj);
293                         jsonVal.setProperty(QLatin1String("type"), QJSValue(QLatin1String("response")));
294
295                         const int sequence = reqMap.value(QLatin1String("seq")).toInt();
296                         jsonVal.setProperty(QLatin1String("request_seq"), QJSValue(sequence));
297                         jsonVal.setProperty(QLatin1String("command"), QJSValue(debugCommand));
298
299                         //Check that the function starts with 'on'
300                         QString eventName(arguments.value(QLatin1String("target")).toString());
301
302
303                         if (eventName.startsWith(QLatin1String("on"))) {
304                             d->eventList.insert(-sequence, eventName.remove(0,2).toLower());
305
306                             QJSValue args = parser.call(QJSValue(), QJSValueList() << obj);
307
308                             args.setProperty(QLatin1String("type"), QJSValue(QLatin1String("event")));
309                             args.setProperty(QLatin1String("breakpoint"), QJSValue(-sequence));
310
311                             jsonVal.setProperty(QLatin1String("body"), args);
312                             jsonVal.setProperty(QLatin1String("success"), QJSValue(true));
313
314                         } else {
315                             jsonVal.setProperty(QLatin1String("success"), QJSValue(false));
316                         }
317
318
319                         jsonVal.setProperty(QLatin1String("running"), QJSValue(!d->loop.isRunning()));
320
321                         QJSValue stringify = d->engine->evaluate(QLatin1String("JSON.stringify"));
322                         QJSValue json = stringify.call(QJSValue(), QJSValueList() << jsonVal);
323                         debugMessageHandler(json.toString());
324
325                     }
326                 }
327             } else if (debugCommand == QLatin1String("clearbreakpoint")) {
328                 //check if the breakpoint is a negative integer (event breakpoint)
329                 const QVariantMap arguments = reqMap.value(QLatin1String("arguments")).toMap();
330                 const int bp = arguments.value(QLatin1String("breakpoint")).toInt();
331
332                 if (bp < 0) {
333                     d->eventList.remove(bp);
334                     forwardRequestToV8 = false;
335                 }
336             }
337             if (forwardRequestToV8)
338                 d->sendDebugMessage(request);
339         }
340     }
341
342     QDeclarativeDebugService::messageReceived(message);
343 }
344
345 void QV8DebugServicePrivate::sendDebugMessage(const QString &message)
346 {
347     if (loop.isRunning())
348         loop.exit();
349
350     v8::Debug::SendCommand(message.utf16(), message.size());
351 }
352
353 QByteArray QV8DebugServicePrivate::packMessage(const QString &message)
354 {
355     QByteArray reply;
356     QDataStream rs(&reply, QIODevice::WriteOnly);
357     QByteArray cmd("V8DEBUG");
358     rs << cmd << message.toUtf8();
359     return reply;
360 }
361
362 QT_END_NAMESPACE