1/*
2    Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies)
3
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13
14    You should have received a copy of the GNU Library General Public License
15    along with this library; see the file COPYING.LIB.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17    Boston, MA 02110-1301, USA.
18*/
19
20#include "../testwindow.h"
21#include "../util.h"
22
23#include <QNetworkAccessManager>
24#include <QNetworkReply>
25#include <QNetworkRequest>
26#include <QScopedPointer>
27#include <QtQml/QQmlEngine>
28#include <QtTest/QtTest>
29#include <private/qquickwebview_p.h>
30#include <private/qwebpreferences_p.h>
31
32#define INSPECTOR_SERVER_PORT "23654"
33static const QUrl s_inspectorServerHttpBaseUrl("http://localhost:" INSPECTOR_SERVER_PORT);
34static const QUrl s_inspectorServerWebSocketBaseUrl("ws://localhost:" INSPECTOR_SERVER_PORT);
35
36class tst_InspectorServer : public QObject {
37    Q_OBJECT
38public:
39    tst_InspectorServer();
40
41private Q_SLOTS:
42    void init();
43    void cleanup();
44
45    void testPageList();
46    void testRemoteDebuggingMessage();
47    void openRemoteDebuggingSession();
48private:
49    void prepareWebViewComponent();
50    inline QQuickWebView* newWebView();
51    inline QQuickWebView* webView() const;
52    QJsonArray fetchPageList() const;
53    QScopedPointer<TestWindow> m_window;
54    QScopedPointer<QQmlComponent> m_component;
55};
56
57tst_InspectorServer::tst_InspectorServer()
58{
59    qputenv("QTWEBKIT_INSPECTOR_SERVER", INSPECTOR_SERVER_PORT);
60    addQtWebProcessToPath();
61    prepareWebViewComponent();
62}
63
64void tst_InspectorServer::prepareWebViewComponent()
65{
66    static QQmlEngine* engine = new QQmlEngine(this);
67    engine->addImportPath(QString::fromUtf8(IMPORT_DIR));
68
69    m_component.reset(new QQmlComponent(engine, this));
70
71    m_component->setData(QByteArrayLiteral("import QtQuick 2.0\n"
72                                           "import QtWebKit 3.0\n"
73                                           "WebView {}")
74                         , QUrl());
75}
76
77QQuickWebView* tst_InspectorServer::newWebView()
78{
79    QObject* viewInstance = m_component->create();
80
81    return qobject_cast<QQuickWebView*>(viewInstance);
82}
83
84void tst_InspectorServer::init()
85{
86    m_window.reset(new TestWindow(newWebView()));
87    webView()->experimental()->preferences()->setDeveloperExtrasEnabled(true);
88}
89
90void tst_InspectorServer::cleanup()
91{
92    m_window.reset();
93}
94
95inline QQuickWebView* tst_InspectorServer::webView() const
96{
97    return static_cast<QQuickWebView*>(m_window->webView.data());
98}
99
100QJsonArray tst_InspectorServer::fetchPageList() const
101{
102    QNetworkAccessManager qnam;
103    QScopedPointer<QNetworkReply> reply(qnam.get(QNetworkRequest(s_inspectorServerHttpBaseUrl.resolved(QUrl("pagelist.json")))));
104    waitForSignal(reply.data(), SIGNAL(finished()));
105    return QJsonDocument::fromJson(reply->readAll()).array();
106}
107
108void tst_InspectorServer::testPageList()
109{
110    QUrl testPageUrl = QUrl::fromLocalFile(QLatin1String(TESTS_SOURCE_DIR "/html/basic_page.html"));
111    LoadStartedCatcher catcher(webView());
112    webView()->setUrl(testPageUrl);
113    waitForSignal(&catcher, SIGNAL(finished()));
114
115    // Our page has developerExtrasEnabled and should be the only one in the list.
116    QJsonArray pageList = fetchPageList();
117    QCOMPARE(pageList.size(), 1);
118    QCOMPARE(testPageUrl.toString(), pageList.at(0).toObject().value("url").toString());
119}
120
121void tst_InspectorServer::testRemoteDebuggingMessage()
122{
123    QJsonArray pageList = fetchPageList();
124    QCOMPARE(pageList.size(), 1);
125
126    // Test sending a raw remote debugging message through our web socket server.
127    // For this specific message see: http://code.google.com/chrome/devtools/docs/protocol/tot/runtime.html#command-evaluate
128    QLatin1String jsExpression("2 + 2");
129    QLatin1String jsExpressionResult("4");
130    QScopedPointer<QQuickWebView> webSocketQueryWebView(newWebView());
131    webSocketQueryWebView->loadHtml(QString(
132        "<script type=\"text/javascript\">\n"
133        "var socket = new WebSocket('%1/devtools/page/%2');\n"
134        "socket.onmessage = function(message) {\n"
135            "var response = JSON.parse(message.data);\n"
136            "if (response.id === 1)\n"
137                "document.title = response.result.result.value;\n"
138        "}\n"
139        "socket.onopen = function() {\n"
140            "socket.send('{\"id\": 1, \"method\": \"Runtime.evaluate\", \"params\": {\"expression\": \"%3\" } }');\n"
141        "}\n"
142        "</script>")
143        .arg(s_inspectorServerWebSocketBaseUrl.toString())
144        .arg(pageList.at(0).toObject().value("id").toDouble())
145        .arg(jsExpression));
146
147    for (int i = 0; i < 10; ++i) {
148        if (!webSocketQueryWebView->title().isEmpty())
149            break;
150        waitForSignal(webSocketQueryWebView.data(), SIGNAL(titleChanged()), 500);
151    }
152
153    QCOMPARE(webSocketQueryWebView->title(), jsExpressionResult);
154}
155
156void tst_InspectorServer::openRemoteDebuggingSession()
157{
158    QJsonArray pageList = fetchPageList();
159    QCOMPARE(pageList.size(), 1);
160
161    QScopedPointer<QQuickWebView> inspectorWebView(newWebView());
162    LoadStartedCatcher catcher2(inspectorWebView.data());
163    inspectorWebView->setUrl(s_inspectorServerHttpBaseUrl.resolved(QUrl(pageList.at(0).toObject().value("inspectorUrl").toString())));
164    waitForSignal(&catcher2, SIGNAL(finished()));
165    for (int i = 0; i < 10; ++i) {
166        if (!inspectorWebView->title().isEmpty())
167            break;
168        waitForSignal(inspectorWebView.data(), SIGNAL(titleChanged()), 500);
169    }
170
171    // To test the whole pipeline this exploits a behavior of the inspector front-end which won't provide any title unless the
172    // debugging session was established correctly through web socket. It should be something like "Web Inspector - <Page URL>".
173    // So this test case will fail if:
174    // - The page list didn't return a valid inspector URL
175    // - Or the front-end couldn't be loaded through the inspector HTTP server
176    // - Or the web socket connection couldn't be established between the front-end and the page through the inspector server
177    // Let's see if this test isn't raising too many false positives, in which case we should use a better predicate if available.
178    QVERIFY(!inspectorWebView->title().isEmpty());
179}
180
181QTEST_MAIN(tst_InspectorServer)
182
183#include "tst_inspectorserver.moc"
184