Fix focus chain handling and cycling through focusable objects (links) using tab...
[WebKit-https.git] / WebKitTools / DumpRenderTree / qt / DumpRenderTree.cpp
1 /*
2  * Copyright (C) 2005, 2006 Apple Computer, Inc.  All rights reserved.
3  * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer.
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include "DumpRenderTree.h"
31 #include "jsobjects.h"
32
33 #include <QDir>
34 #include <QFile>
35 #include <QTimer>
36 #include <QBoxLayout>
37 #include <QScrollArea>
38 #include <QApplication>
39 #include <QUrl>
40 #include <QFocusEvent>
41
42 #include <qwebpage.h>
43 #include <qwebframe.h>
44 #include <qwebview.h>
45 #include <qwebsettings.h>
46
47 #include <unistd.h>
48 #include <qdebug.h>
49 extern void qt_drt_run(bool b);
50 extern void qt_dump_set_accepts_editing(bool b);
51
52
53 namespace WebCore {
54
55 // Choose some default values.
56 const unsigned int maxViewWidth = 800;
57 const unsigned int maxViewHeight = 600;
58
59 class WebPage : public QWebPage {
60     Q_OBJECT
61 public:
62     WebPage(QWidget *parent, DumpRenderTree *drt);
63
64     QWebPage *createWindow();
65
66     void javaScriptAlert(QWebFrame *frame, const QString& message);
67     void javaScriptConsoleMessage(const QString& message, unsigned int lineNumber, const QString& sourceID);
68     bool javaScriptConfirm(QWebFrame *frame, const QString& msg);
69     bool javaScriptPrompt(QWebFrame *frame, const QString& msg, const QString& defaultValue, QString* result);
70
71 private slots:
72     void setViewGeometry(const QRect &r)
73     {
74         QWidget *v = view();
75         if (v)
76             v->setGeometry(r);
77     }
78 private:
79     DumpRenderTree *m_drt;
80 };
81
82 WebPage::WebPage(QWidget *parent, DumpRenderTree *drt)
83     : QWebPage(parent), m_drt(drt)
84 {
85     settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, true);
86     settings()->setAttribute(QWebSettings::JavascriptCanAccessClipboard, true);
87     settings()->setAttribute(QWebSettings::LinksIncludedInFocusChain, false);
88     connect(this, SIGNAL(geometryChangeRequest(const QRect &)),
89             this, SLOT(setViewGeometry(const QRect & )));
90 }
91
92 QWebPage *WebPage::createWindow()
93 {
94     return m_drt->createWindow();
95 }
96
97 void WebPage::javaScriptAlert(QWebFrame *frame, const QString& message)
98 {
99     fprintf(stdout, "ALERT: %s\n", message.toUtf8().constData());
100 }
101
102 void WebPage::javaScriptConsoleMessage(const QString& message, unsigned int lineNumber, const QString&)
103 {
104     fprintf (stdout, "CONSOLE MESSAGE: line %d: %s\n", lineNumber, message.toUtf8().constData());
105 }
106
107 bool WebPage::javaScriptConfirm(QWebFrame *frame, const QString& msg)
108 {
109     fprintf(stdout, "CONFIRM: %s\n", msg.toUtf8().constData());
110     return true;
111 }
112
113 bool WebPage::javaScriptPrompt(QWebFrame *frame, const QString& msg, const QString& defaultValue, QString* result)
114 {
115     fprintf(stdout, "PROMPT: %s, default text: %s\n", msg.toUtf8().constData(), defaultValue.toUtf8().constData());
116     *result = defaultValue;
117     return true;
118 }
119
120 DumpRenderTree::DumpRenderTree()
121     : m_stdin(0)
122     , m_notifier(0)
123 {
124     m_controller = new LayoutTestController(this);
125     connect(m_controller, SIGNAL(done()), this, SLOT(dump()), Qt::QueuedConnection);
126
127     QWebView *view = new QWebView(0);
128     view->resize(QSize(maxViewWidth, maxViewHeight));
129     m_page = new WebPage(view, this);
130     view->setPage(m_page);
131     connect(m_page, SIGNAL(frameCreated(QWebFrame *)), this, SLOT(connectFrame(QWebFrame *)));
132     connectFrame(m_page->mainFrame());
133     
134     m_page->mainFrame()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
135     m_page->mainFrame()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
136     connect(m_page->mainFrame(), SIGNAL(titleChanged(const QString&)),
137             SLOT(titleChanged(const QString&)));
138
139     m_eventSender = new EventSender(m_page);
140     m_textInputController = new TextInputController(m_page);
141
142     QObject::connect(this, SIGNAL(quit()), qApp, SLOT(quit()), Qt::QueuedConnection);
143     qt_drt_run(true);
144     QFocusEvent event(QEvent::FocusIn, Qt::ActiveWindowFocusReason);
145     QApplication::sendEvent(view, &event);
146 }
147
148 DumpRenderTree::~DumpRenderTree()
149 {
150     delete m_page;
151
152     delete m_stdin;
153     delete m_notifier;
154 }
155
156 void DumpRenderTree::open()
157 {
158     if (!m_stdin) {
159         m_stdin = new QFile;
160         m_stdin->open(stdin, QFile::ReadOnly);
161     }
162
163     if (!m_notifier) {
164         m_notifier = new QSocketNotifier(STDIN_FILENO, QSocketNotifier::Read);
165         connect(m_notifier, SIGNAL(activated(int)), this, SLOT(readStdin(int)));
166     }
167 }
168
169 void DumpRenderTree::open(const QUrl& url)
170 {
171     resetJSObjects();
172     m_page->mainFrame()->load(url);
173 }
174
175 void DumpRenderTree::readStdin(int /* socket */)
176 {
177     // Read incoming data from stdin...
178     QByteArray line = m_stdin->readLine();
179     if (line.endsWith('\n'))
180         line.truncate(line.size()-1);
181     //fprintf(stderr, "\n    opening %s\n", line.constData());
182     if (line.isEmpty())
183         quit();
184     QFileInfo fi(line);
185     open(QUrl::fromLocalFile(fi.absoluteFilePath()));
186     fflush(stdout);
187 }
188
189 void DumpRenderTree::resetJSObjects()
190 {
191     m_controller->reset();
192     foreach(QWidget *widget, windows)
193         delete widget;
194     windows.clear();
195 }
196
197 void DumpRenderTree::initJSObjects()
198 {
199     QWebFrame *frame = qobject_cast<QWebFrame*>(sender());
200     Q_ASSERT(frame);
201     frame->addToJSWindowObject(QLatin1String("layoutTestController"), m_controller);
202     frame->addToJSWindowObject(QLatin1String("eventSender"), m_eventSender);
203     frame->addToJSWindowObject(QLatin1String("textInputController"), m_textInputController);
204 }
205
206
207 QString DumpRenderTree::dumpFramesAsText(QWebFrame* frame)
208 {
209     if (!frame)
210         return QString();
211
212     QString result;
213     QWebFrame *parent = qobject_cast<QWebFrame *>(frame->parent());
214     if (parent) {
215         result.append(QLatin1String("\n--------\nFrame: '"));
216         result.append(frame->name());
217         result.append(QLatin1String("'\n--------\n"));
218     }
219
220     result.append(frame->innerText());
221     result.append(QLatin1String("\n"));
222
223     if (m_controller->shouldDumpChildrenAsText()) {
224         QList<QWebFrame *> children = frame->childFrames();
225         for (int i = 0; i < children.size(); ++i)
226             result += dumpFramesAsText(children.at(i));
227     }
228
229     return result;
230 }
231
232 void DumpRenderTree::dump()
233 {
234     QWebFrame *frame = m_page->mainFrame();
235
236     //fprintf(stderr, "    Dumping\n");
237     if (!m_notifier) {
238         // Dump markup in single file mode...
239         QString markup = frame->markup();
240         fprintf(stdout, "Source:\n\n%s\n", markup.toUtf8().constData());
241     }
242
243     // Dump render text...
244     QString renderDump;
245     if (m_controller->shouldDumpAsText()) {
246         renderDump = dumpFramesAsText(frame);
247     } else {
248         renderDump = frame->renderTreeDump();
249     }
250     if (renderDump.isEmpty()) {
251         printf("ERROR: nil result from %s", m_controller->shouldDumpAsText() ? "[documentElement innerText]" : "[frame renderTreeAsExternalRepresentation]");
252     } else {
253         fprintf(stdout, "%s", renderDump.toUtf8().constData());
254     }
255
256     fprintf(stdout, "#EOF\n");
257
258     fflush(stdout);
259
260     if (!m_notifier) {
261         // Exit now in single file mode...
262         quit();
263     }
264 }
265
266 void DumpRenderTree::titleChanged(const QString &s)
267 {
268     if (m_controller->shouldDumpTitleChanges())
269         printf("TITLE CHANGED: %s\n", s.toUtf8().data());
270 }
271
272 void DumpRenderTree::connectFrame(QWebFrame *frame)
273 {
274     connect(frame, SIGNAL(cleared()), this, SLOT(initJSObjects()));
275     connect(frame, SIGNAL(provisionalLoad()),
276             layoutTestController(), SLOT(provisionalLoad()));
277
278     if (frame == m_page->mainFrame()) {
279         connect(frame, SIGNAL(loadDone(bool)),
280                 layoutTestController(), SLOT(maybeDump(bool)));
281     }
282 }
283
284 QWebPage *DumpRenderTree::createWindow()
285 {
286     if (!m_controller->canOpenWindows())
287         return 0;
288     QWidget *container = new QWidget(0);
289     container->resize(0, 0);
290     container->move(-1, -1);
291     container->hide();
292     QWebPage *page = new WebPage(container, this);
293     connect(m_page, SIGNAL(frameCreated(QWebFrame *)), this, SLOT(connectFrame(QWebFrame *)));
294     windows.append(container);
295     return page;
296 }
297
298 int DumpRenderTree::windowCount() const
299 {
300     int count = 0;
301     foreach(QWidget *w, windows) {
302         if (w->children().count())
303             ++count;
304     }
305     return count + 1;
306 }
307
308 }
309
310 #include "DumpRenderTree.moc"