Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit.

  • Loading branch information...
commit c64385ad35805f0389055f4714eb2c47027a43dc 0 parents
Tristan Dunn authored
Showing with 1,445 additions and 0 deletions.
  1. +7 −0 .gitignore
  2. +22 −0 LICENSE
  3. +33 −0 README.markdown
  4. +22 −0 bin/Info.plist
  5. +37 −0 build.sh
  6. +12 −0 src/Body.h
  7. +15 −0 src/Command.cpp
  8. +29 −0 src/Command.h
  9. +145 −0 src/Connection.cpp
  10. +39 −0 src/Connection.h
  11. +84 −0 src/Evaluate.cpp
  12. +22 −0 src/Evaluate.h
  13. +16 −0 src/Execute.cpp
  14. +12 −0 src/Execute.h
  15. +19 −0 src/Find.cpp
  16. +13 −0 src/Find.h
  17. +66 −0 src/FrameFocus.cpp
  18. +28 −0 src/FrameFocus.h
  19. +18 −0 src/Header.cpp
  20. +11 −0 src/Header.h
  21. +14 −0 src/JavascriptInvocation.cpp
  22. +19 −0 src/JavascriptInvocation.h
  23. +22 −0 src/NetworkAccessManager.cpp
  24. +18 −0 src/NetworkAccessManager.h
  25. +14 −0 src/Node.cpp
  26. +13 −0 src/Node.h
  27. +19 −0 src/Render.cpp
  28. +12 −0 src/Render.h
  29. +18 −0 src/Reset.cpp
  30. +12 −0 src/Reset.h
  31. +19 −0 src/Response.cpp
  32. +13 −0 src/Response.h
  33. +24 −0 src/Server.cpp
  34. +21 −0 src/Server.h
  35. +20 −0 src/Source.cpp
  36. +19 −0 src/Source.h
  37. +15 −0 src/Url.cpp
  38. +12 −0 src/Url.h
  39. +24 −0 src/Visit.cpp
  40. +15 −0 src/Visit.h
  41. +154 −0 src/WebPage.cpp
  42. +37 −0 src/WebPage.h
  43. +11 −0 src/body.cpp
  44. +194 −0 src/capybara.js
  45. +17 −0 src/find_command.h
  46. +21 −0 src/main.cpp
  47. +10 −0 src/webkit_server.pro
  48. +5 −0 src/webkit_server.qrc
  49. +3 −0  webkit_server.pro
7 .gitignore
@@ -0,0 +1,7 @@
+*.o
+*.moc
+Makefile*
+qrc_*
+moc_*.cpp
+bin/webkit_server
+src/webkit_server
22 LICENSE
@@ -0,0 +1,22 @@
+The MIT License
+
+Copyright (c) 2011 thoughtbot, inc.
+Copyright (c) 2011 Tristan Dunn
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
33 README.markdown
@@ -0,0 +1,33 @@
+# webkit-server
+
+A standalone version of the WebKit server in [capybara-webkit](https://github.com/thoughtbot/capybara-webkit).
+
+## Dependencies
+
+webkit-server depends on a WebKit implementation from Qt, a cross-platform development toolkit. You'll need to download the Qt libraries to build the binary.
+
+**OS X:**
+
+[Download the non-debug Cocoa package](http://qt.nokia.com/downloads/qt-for-open-source-cpp-development-on-mac-os-x). Note that installing Qt via homebrew takes more than an hour, so it is not recommended.
+
+**Ubuntu:**
+
+ apt-get install libqt4-dev
+
+**Fedora:**
+
+ yum install qt-webkit-devel
+
+**Other Linux distributions:**
+
+[Download this package](http://qt.nokia.com/downloads/linux-x11-cpp).
+
+## Building
+
+To generate the necessary files and build the binary:
+
+ ./build.sh
+
+## License
+
+webkit-server uses the MIT license. See LICENSE for more details.
22 bin/Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<plist version="0.9">
+ <dict>
+ <key>CFBundleIconFile</key>
+ <string></string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleGetInfoString</key>
+ <string>Created by Qt/QMake</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleExecutable</key>
+ <string>webkit_server</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.yourcompany.webkit_server</string>
+ <key>NOTE</key>
+ <string>This file was generated by Qt/QMake.</string>
+ <key>LSUIElement</key>
+ <string>1</string>
+ </dict>
+</plist>
37 build.sh
@@ -0,0 +1,37 @@
+if hash qmake 2> /dev/null; then
+ QMAKE_BINARY="qmake"
+elif hash qmake-qt4 2> /dev/null; then
+ QMAKE_BINARY="qmake-qt4"
+else
+ echo "The qmake or qmake-qt4 binary could not be found. Please ensure Qt is installed and in your PATH."
+ exit 1
+fi
+
+PLATFORM=`uname -s`
+
+if [ $PLATFORM == "Linux" ]; then
+ SPEC="linux-g++"
+elif [ $PLATFORM == "FreeBSD" ]; then
+ SPEC="freebsd-g++"
+elif [ $PLATFORM == "Darwin" ]; then
+ SPEC="macx-g++"
+else
+ echo "The $PLATFORM platform is not currently supported."
+ exit 2
+fi
+
+if hash gmake 2> /dev/null; then
+ MAKE_BINARY="gmake"
+elif hash make 2> /dev/null; then
+ MAKE_BINARY="make"
+else
+ echo "The make or gmake binary count not be found. Please ensure it is in your PATH."
+ exit 3
+fi
+
+$QMAKE_BINARY -spec $SPEC
+$MAKE_BINARY qmake
+$MAKE_BINARY
+
+mkdir -p bin
+cp src/webkit_server bin/
12 src/Body.h
@@ -0,0 +1,12 @@
+#include "Command.h"
+
+class WebPage;
+
+class Body : public Command {
+ Q_OBJECT
+
+ public:
+ Body(WebPage *page, QObject *parent = 0);
+ virtual void start(QStringList &arguments);
+};
+
15 src/Command.cpp
@@ -0,0 +1,15 @@
+#include "Command.h"
+#include "WebPage.h"
+
+Command::Command(WebPage *page, QObject *parent) : QObject(parent) {
+ m_page = page;
+}
+
+void Command::start(QStringList &arguments) {
+ Q_UNUSED(arguments);
+}
+
+WebPage *Command::page() {
+ return m_page;
+}
+
29 src/Command.h
@@ -0,0 +1,29 @@
+#ifndef COMMAND_H
+#define COMMAND_H
+
+#include <QObject>
+#include <QStringList>
+#include "Response.h"
+
+class WebPage;
+
+class Command : public QObject {
+ Q_OBJECT
+
+ public:
+ Command(WebPage *page, QObject *parent = 0);
+ virtual void start(QStringList &arguments);
+
+ signals:
+ void finished(Response *response);
+
+ protected:
+ WebPage *page();
+
+ private:
+ WebPage *m_page;
+
+};
+
+#endif
+
145 src/Connection.cpp
@@ -0,0 +1,145 @@
+#include "Connection.h"
+#include "WebPage.h"
+#include "Visit.h"
+#include "Find.h"
+#include "Command.h"
+#include "Reset.h"
+#include "Node.h"
+#include "Url.h"
+#include "Source.h"
+#include "Evaluate.h"
+#include "Execute.h"
+#include "FrameFocus.h"
+#include "Header.h"
+#include "Render.h"
+#include "Body.h"
+
+#include <QTcpSocket>
+#include <iostream>
+
+Connection::Connection(QTcpSocket *socket, WebPage *page, QObject *parent) :
+ QObject(parent) {
+ m_socket = socket;
+ m_page = page;
+ m_command = NULL;
+ m_expectingDataSize = -1;
+ m_pageSuccess = true;
+ m_commandWaiting = false;
+ connect(m_socket, SIGNAL(readyRead()), this, SLOT(checkNext()));
+ connect(m_page, SIGNAL(loadFinished(bool)), this, SLOT(pendingLoadFinished(bool)));
+}
+
+void Connection::checkNext() {
+ if (m_expectingDataSize == -1) {
+ if (m_socket->canReadLine()) {
+ readLine();
+ checkNext();
+ }
+ } else {
+ if (m_socket->bytesAvailable() >= m_expectingDataSize) {
+ readDataBlock();
+ checkNext();
+ }
+ }
+}
+
+void Connection::readLine() {
+ char buffer[128];
+ qint64 lineLength = m_socket->readLine(buffer, 128);
+ if (lineLength != -1) {
+ buffer[lineLength - 1] = 0;
+ processNext(buffer);
+ }
+}
+
+void Connection::readDataBlock() {
+ char *buffer = new char[m_expectingDataSize + 1];
+ m_socket->read(buffer, m_expectingDataSize);
+ buffer[m_expectingDataSize] = 0;
+ processNext(buffer);
+ m_expectingDataSize = -1;
+ delete[] buffer;
+}
+
+void Connection::processNext(const char *data) {
+ if (m_commandName.isNull()) {
+ m_commandName = data;
+ m_argumentsExpected = -1;
+ } else {
+ processArgument(data);
+ }
+}
+
+void Connection::processArgument(const char *data) {
+ if (m_argumentsExpected == -1) {
+ m_argumentsExpected = QString(data).toInt();
+ } else if (m_expectingDataSize == -1) {
+ m_expectingDataSize = QString(data).toInt();
+ } else {
+ m_arguments.append(QString::fromUtf8(data));
+ }
+
+ if (m_arguments.length() == m_argumentsExpected) {
+ if (m_page->isLoading())
+ m_commandWaiting = true;
+ else
+ startCommand();
+ }
+}
+
+void Connection::startCommand() {
+ m_commandWaiting = false;
+ if (m_pageSuccess) {
+ m_command = createCommand(m_commandName.toAscii().constData());
+ if (m_command) {
+ connect(m_command,
+ SIGNAL(finished(Response *)),
+ this,
+ SLOT(finishCommand(Response *)));
+ m_command->start(m_arguments);
+ } else {
+ QString failure = QString("[Capybara WebKit] Unknown command: ") + m_commandName + "\n";
+ writeResponse(new Response(false, failure));
+ }
+ m_commandName = QString();
+ } else {
+ m_pageSuccess = true;
+ QString message = m_page->failureString();
+ writeResponse(new Response(false, message));
+ }
+}
+
+Command *Connection::createCommand(const char *name) {
+ #include "find_command.h"
+ return NULL;
+}
+
+void Connection::pendingLoadFinished(bool success) {
+ m_pageSuccess = success;
+ if (m_commandWaiting)
+ startCommand();
+}
+
+void Connection::finishCommand(Response *response) {
+ m_command->deleteLater();
+ m_command = NULL;
+ writeResponse(response);
+}
+
+void Connection::writeResponse(Response *response) {
+ if (response->isSuccess())
+ m_socket->write("ok\n");
+ else
+ m_socket->write("failure\n");
+
+ QByteArray messageUtf8 = response->message().toUtf8();
+ QString messageLength = QString::number(messageUtf8.size()) + "\n";
+ m_socket->write(messageLength.toAscii());
+ m_socket->write(messageUtf8);
+ delete response;
+
+ m_arguments.clear();
+ m_commandName = QString();
+ m_argumentsExpected = -1;
+}
+
39 src/Connection.h
@@ -0,0 +1,39 @@
+#include <QObject>
+#include <QStringList>
+
+class QTcpSocket;
+class WebPage;
+class Command;
+class Response;
+
+class Connection : public QObject {
+ Q_OBJECT
+
+ public:
+ Connection(QTcpSocket *socket, WebPage *page, QObject *parent = 0);
+
+ public slots:
+ void checkNext();
+ void finishCommand(Response *response);
+ void pendingLoadFinished(bool success);
+
+ private:
+ void readLine();
+ void readDataBlock();
+ void processNext(const char *line);
+ Command *createCommand(const char *name);
+ void processArgument(const char *line);
+ void startCommand();
+ void writeResponse(Response *response);
+
+ QTcpSocket *m_socket;
+ QString m_commandName;
+ Command *m_command;
+ QStringList m_arguments;
+ int m_argumentsExpected;
+ WebPage *m_page;
+ int m_expectingDataSize;
+ bool m_pageSuccess;
+ bool m_commandWaiting;
+};
+
84 src/Evaluate.cpp
@@ -0,0 +1,84 @@
+#include "Evaluate.h"
+#include "WebPage.h"
+#include <iostream>
+
+Evaluate::Evaluate(WebPage *page, QObject *parent) : Command(page, parent) {
+ m_buffer = "";
+}
+
+void Evaluate::start(QStringList &arguments) {
+ QVariant result = page()->currentFrame()->evaluateJavaScript(arguments[0]);
+ addVariant(result);
+ emit finished(new Response(true, m_buffer));
+}
+
+void Evaluate::addVariant(QVariant &object) {
+ if (object.isValid()) {
+ switch(object.type()) {
+ case QMetaType::QString:
+ {
+ QString string = object.toString();
+ addString(string);
+ }
+ break;
+ case QMetaType::QVariantList:
+ {
+ QVariantList list = object.toList();
+ addArray(list);
+ }
+ break;
+ case QMetaType::Double:
+ m_buffer.append(object.toString());
+ break;
+ case QMetaType::QVariantMap:
+ {
+ QVariantMap map = object.toMap();
+ addMap(map);
+ break;
+ }
+ case QMetaType::Bool:
+ {
+ m_buffer.append(object.toString());
+ break;
+ }
+ default:
+ m_buffer.append("null");
+ }
+ } else {
+ m_buffer.append("null");
+ }
+}
+
+void Evaluate::addString(QString &string) {
+ QString escapedString(string);
+ escapedString.replace("\"", "\\\"");
+ m_buffer.append("\"");
+ m_buffer.append(escapedString);
+ m_buffer.append("\"");
+}
+
+void Evaluate::addArray(QVariantList &list) {
+ m_buffer.append("[");
+ for (int i = 0; i < list.length(); i++) {
+ if (i > 0)
+ m_buffer.append(",");
+ addVariant(list[i]);
+ }
+ m_buffer.append("]");
+}
+
+void Evaluate::addMap(QVariantMap &map) {
+ m_buffer.append("{");
+ QMapIterator<QString, QVariant> iterator(map);
+ while (iterator.hasNext()) {
+ iterator.next();
+ QString key = iterator.key();
+ QVariant value = iterator.value();
+ addString(key);
+ m_buffer.append(":");
+ addVariant(value);
+ if (iterator.hasNext())
+ m_buffer.append(",");
+ }
+ m_buffer.append("}");
+}
22 src/Evaluate.h
@@ -0,0 +1,22 @@
+#include "Command.h"
+
+#include <QVariantList>
+
+class WebPage;
+
+class Evaluate : public Command {
+ Q_OBJECT
+
+ public:
+ Evaluate(WebPage *page, QObject *parent = 0);
+ virtual void start(QStringList &arguments);
+
+ private:
+ void addVariant(QVariant &object);
+ void addString(QString &string);
+ void addArray(QVariantList &list);
+ void addMap(QVariantMap &map);
+
+ QString m_buffer;
+};
+
16 src/Execute.cpp
@@ -0,0 +1,16 @@
+#include "Execute.h"
+#include "WebPage.h"
+
+Execute::Execute(WebPage *page, QObject *parent) : Command(page, parent) {
+}
+
+void Execute::start(QStringList &arguments) {
+ QString script = arguments[0] + QString("; 'success'");
+ QVariant result = page()->currentFrame()->evaluateJavaScript(script);
+ if (result.isValid()) {
+ emit finished(new Response(true));
+ } else {
+ emit finished(new Response(false, "Javascript failed to execute"));
+ }
+}
+
12 src/Execute.h
@@ -0,0 +1,12 @@
+#include "Command.h"
+
+class WebPage;
+
+class Execute : public Command {
+ Q_OBJECT
+
+ public:
+ Execute(WebPage *page, QObject *parent = 0);
+ virtual void start(QStringList &arguments);
+};
+
19 src/Find.cpp
@@ -0,0 +1,19 @@
+#include "Find.h"
+#include "Command.h"
+#include "WebPage.h"
+
+Find::Find(WebPage *page, QObject *parent) : Command(page, parent) {
+}
+
+void Find::start(QStringList &arguments) {
+ QString message;
+ QVariant result = page()->invokeCapybaraFunction("find", arguments);
+
+ if (result.isValid()) {
+ message = result.toString();
+ emit finished(new Response(true, message));
+ } else {
+ emit finished(new Response(false, "Invalid XPath expression"));
+ }
+}
+
13 src/Find.h
@@ -0,0 +1,13 @@
+#include "Command.h"
+
+class WebPage;
+
+class Find : public Command {
+ Q_OBJECT
+
+ public:
+ Find(WebPage *page, QObject *parent = 0);
+ virtual void start(QStringList &arguments);
+};
+
+
66 src/FrameFocus.cpp
@@ -0,0 +1,66 @@
+#include "FrameFocus.h"
+#include "Command.h"
+#include "WebPage.h"
+
+FrameFocus::FrameFocus(WebPage *page, QObject *parent) : Command(page, parent) {
+}
+
+void FrameFocus::start(QStringList &arguments) {
+ findFrames();
+ switch(arguments.length()) {
+ case 1:
+ focusId(arguments[0]);
+ break;
+ case 2:
+ focusIndex(arguments[1].toInt());
+ break;
+ default:
+ focusParent();
+ }
+}
+
+void FrameFocus::findFrames() {
+ frames = page()->currentFrame()->childFrames();
+}
+
+void FrameFocus::focusIndex(int index) {
+ if (isFrameAtIndex(index)) {
+ frames[index]->setFocus();
+ success();
+ } else {
+ frameNotFound();
+ }
+}
+
+bool FrameFocus::isFrameAtIndex(int index) {
+ return 0 <= index && index < frames.length();
+}
+
+void FrameFocus::focusId(QString name) {
+ for (int i = 0; i < frames.length(); i++) {
+ if (frames[i]->frameName().compare(name) == 0) {
+ frames[i]->setFocus();
+ success();
+ return;
+ }
+ }
+
+ frameNotFound();
+}
+
+void FrameFocus::focusParent() {
+ if (page()->currentFrame()->parentFrame() == 0) {
+ emit finished(new Response(false, "Already at parent frame."));
+ } else {
+ page()->currentFrame()->parentFrame()->setFocus();
+ success();
+ }
+}
+
+void FrameFocus::frameNotFound() {
+ emit finished(new Response(false, "Unable to locate frame. "));
+}
+
+void FrameFocus::success() {
+ emit finished(new Response(true));
+}
28 src/FrameFocus.h
@@ -0,0 +1,28 @@
+#include "Command.h"
+
+class WebPage;
+class QWebFrame;
+
+class FrameFocus : public Command {
+ Q_OBJECT
+
+ public:
+ FrameFocus(WebPage *page, QObject *parent = 0);
+ virtual void start(QStringList &arguments);
+
+ private:
+ void findFrames();
+
+ void focusParent();
+
+ void focusIndex(int index);
+ bool isFrameAtIndex(int index);
+
+ void focusId(QString id);
+
+ void success();
+ void frameNotFound();
+
+ QList<QWebFrame *> frames;
+};
+
18 src/Header.cpp
@@ -0,0 +1,18 @@
+#include "Header.h"
+#include "WebPage.h"
+#include "NetworkAccessManager.h"
+
+Header::Header(WebPage *page, QObject *parent) : Command(page, parent) {
+}
+
+void Header::start(QStringList &arguments) {
+ QString key = arguments[0];
+ QString value = arguments[1];
+ NetworkAccessManager* networkAccessManager = qobject_cast<NetworkAccessManager*>(page()->networkAccessManager());
+ if (key.toLower().replace("-", "_") == "user_agent") {
+ page()->setUserAgent(value);
+ } else {
+ networkAccessManager->addHeader(key, value);
+ }
+ emit finished(new Response(true));
+}
11 src/Header.h
@@ -0,0 +1,11 @@
+#include "Command.h"
+
+class WebPage;
+
+class Header : public Command {
+ Q_OBJECT
+
+ public:
+ Header(WebPage *page, QObject *parent = 0);
+ virtual void start(QStringList &arguments);
+};
14 src/JavascriptInvocation.cpp
@@ -0,0 +1,14 @@
+#include "JavascriptInvocation.h"
+
+JavascriptInvocation::JavascriptInvocation(QString &functionName, QStringList &arguments, QObject *parent) : QObject(parent) {
+ m_functionName = functionName;
+ m_arguments = arguments;
+}
+
+QString &JavascriptInvocation::functionName() {
+ return m_functionName;
+}
+
+QStringList &JavascriptInvocation::arguments() {
+ return m_arguments;
+}
19 src/JavascriptInvocation.h
@@ -0,0 +1,19 @@
+#include <QObject>
+#include <QString>
+#include <QStringList>
+
+class JavascriptInvocation : public QObject {
+ Q_OBJECT
+ Q_PROPERTY(QString functionName READ functionName)
+ Q_PROPERTY(QStringList arguments READ arguments)
+
+ public:
+ JavascriptInvocation(QString &functionName, QStringList &arguments, QObject *parent = 0);
+ QString &functionName();
+ QStringList &arguments();
+
+ private:
+ QString m_functionName;
+ QStringList m_arguments;
+};
+
22 src/NetworkAccessManager.cpp
@@ -0,0 +1,22 @@
+#include "NetworkAccessManager.h"
+#include "WebPage.h"
+#include <iostream>
+
+
+NetworkAccessManager::NetworkAccessManager(QObject *parent):QNetworkAccessManager(parent) {
+}
+
+QNetworkReply* NetworkAccessManager::createRequest(QNetworkAccessManager::Operation oparation, const QNetworkRequest &request, QIODevice * outgoingData = 0) {
+ QNetworkRequest new_request(request);
+ QHashIterator<QString, QString> item(m_headers);
+ while (item.hasNext()) {
+ item.next();
+ new_request.setRawHeader(item.key().toAscii(), item.value().toAscii());
+ }
+ return QNetworkAccessManager::createRequest(oparation, new_request, outgoingData);
+};
+
+void NetworkAccessManager::addHeader(QString key, QString value) {
+ m_headers.insert(key, value);
+};
+
18 src/NetworkAccessManager.h
@@ -0,0 +1,18 @@
+#include <QtNetwork/QNetworkAccessManager>
+#include <QtNetwork/QNetworkRequest>
+#include <QtNetwork/QNetworkReply>
+
+class NetworkAccessManager : public QNetworkAccessManager {
+
+ Q_OBJECT
+
+ public:
+ NetworkAccessManager(QObject *parent = 0);
+ void addHeader(QString key, QString value);
+
+ protected:
+ QNetworkReply* createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice * outgoingData);
+
+ private:
+ QHash<QString, QString> m_headers;
+};
14 src/Node.cpp
@@ -0,0 +1,14 @@
+#include "Node.h"
+#include "WebPage.h"
+
+Node::Node(WebPage *page, QObject *parent) : Command(page, parent) {
+}
+
+void Node::start(QStringList &arguments) {
+ QStringList functionArguments(arguments);
+ QString functionName = functionArguments.takeFirst();
+ QVariant result = page()->invokeCapybaraFunction(functionName, functionArguments);
+ QString attributeValue = result.toString();
+ emit finished(new Response(true, attributeValue));
+}
+
13 src/Node.h
@@ -0,0 +1,13 @@
+#include "Command.h"
+#include <QStringList>
+
+class WebPage;
+
+class Node : public Command {
+ Q_OBJECT
+
+ public:
+ Node(WebPage *page, QObject *parent = 0);
+ virtual void start(QStringList &arguments);
+};
+
19 src/Render.cpp
@@ -0,0 +1,19 @@
+#include "Render.h"
+#include "WebPage.h"
+
+Render::Render(WebPage *page, QObject *parent) : Command(page, parent) {
+}
+
+void Render::start(QStringList &arguments) {
+ QStringList functionArguments(arguments);
+ QString imagePath = functionArguments.takeFirst();
+ int width = functionArguments.takeFirst().toInt();
+ int height = functionArguments.takeFirst().toInt();
+
+ QSize size(width, height);
+ page()->setViewportSize(size);
+
+ bool result = page()->render( imagePath );
+
+ emit finished(new Response(result));
+}
12 src/Render.h
@@ -0,0 +1,12 @@
+#include "Command.h"
+#include <QStringList>
+
+class WebPage;
+
+class Render : public Command {
+ Q_OBJECT
+
+ public:
+ Render(WebPage *page, QObject *parent = 0);
+ virtual void start(QStringList &arguments);
+};
18 src/Reset.cpp
@@ -0,0 +1,18 @@
+#include "Reset.h"
+#include "WebPage.h"
+#include "NetworkAccessManager.h"
+
+Reset::Reset(WebPage *page, QObject *parent) : Command(page, parent) {
+}
+
+void Reset::start(QStringList &arguments) {
+ Q_UNUSED(arguments);
+
+ page()->triggerAction(QWebPage::Stop);
+ page()->currentFrame()->setHtml("<html><body></body></html>");
+ page()->networkAccessManager()->setCookieJar(new QNetworkCookieJar());
+ page()->setNetworkAccessManager(new NetworkAccessManager());
+ page()->setUserAgent(NULL);
+ emit finished(new Response(true));
+}
+
12 src/Reset.h
@@ -0,0 +1,12 @@
+#include "Command.h"
+
+class WebPage;
+
+class Reset : public Command {
+ Q_OBJECT
+
+ public:
+ Reset(WebPage *page, QObject *parent = 0);
+ virtual void start(QStringList &arguments);
+};
+
19 src/Response.cpp
@@ -0,0 +1,19 @@
+#include "Response.h"
+#include <iostream>
+
+Response::Response(bool success, QString message) {
+ m_success = success;
+ m_message = message;
+}
+
+Response::Response(bool success) {
+ m_success = success;
+}
+
+bool Response::isSuccess() const {
+ return m_success;
+}
+
+QString Response::message() const {
+ return m_message;
+}
13 src/Response.h
@@ -0,0 +1,13 @@
+#include <QString>
+
+class Response {
+ public:
+ Response(bool success, QString message);
+ Response(bool success);
+ bool isSuccess() const;
+ QString message() const;
+
+ private:
+ bool m_success;
+ QString m_message;
+};
24 src/Server.cpp
@@ -0,0 +1,24 @@
+#include "Server.h"
+#include "WebPage.h"
+#include "Connection.h"
+
+#include <QTcpServer>
+
+Server::Server(QObject *parent) : QObject(parent) {
+ m_tcp_server = new QTcpServer(this);
+ m_page = new WebPage(this);
+}
+
+bool Server::start() {
+ connect(m_tcp_server, SIGNAL(newConnection()), this, SLOT(handleConnection()));
+ return m_tcp_server->listen(QHostAddress::Any, 0);
+}
+
+quint16 Server::server_port() const {
+ return m_tcp_server->serverPort();
+}
+
+void Server::handleConnection() {
+ QTcpSocket *socket = m_tcp_server->nextPendingConnection();
+ new Connection(socket, m_page, this);
+}
21 src/Server.h
@@ -0,0 +1,21 @@
+#include <QObject>
+
+class QTcpServer;
+class WebPage;
+
+class Server : public QObject {
+ Q_OBJECT
+
+ public:
+ Server(QObject *parent = 0);
+ bool start();
+ quint16 server_port() const;
+
+ public slots:
+ void handleConnection();
+
+ private:
+ QTcpServer *m_tcp_server;
+ WebPage *m_page;
+};
+
20 src/Source.cpp
@@ -0,0 +1,20 @@
+#include "Source.h"
+#include "WebPage.h"
+
+Source::Source(WebPage *page, QObject *parent) : Command(page, parent) {
+}
+
+void Source::start(QStringList &arguments) {
+ Q_UNUSED(arguments)
+
+ QNetworkAccessManager* accessManager = page()->networkAccessManager();
+ QNetworkRequest request(page()->currentFrame()->url());
+ reply = accessManager->get(request);
+
+ connect(reply, SIGNAL(finished()), this, SLOT(sourceLoaded()));
+}
+
+void Source::sourceLoaded() {
+ emit finished(new Response(true, reply->readAll()));
+}
+
19 src/Source.h
@@ -0,0 +1,19 @@
+#include "Command.h"
+
+class WebPage;
+class QNetworkReply;
+
+class Source : public Command {
+ Q_OBJECT
+
+ public:
+ Source(WebPage *page, QObject *parent = 0);
+ virtual void start(QStringList &arguments);
+
+ public slots:
+ void sourceLoaded();
+
+ private:
+ QNetworkReply *reply;
+};
+
15 src/Url.cpp
@@ -0,0 +1,15 @@
+#include "Url.h"
+#include "WebPage.h"
+
+Url::Url(WebPage *page, QObject *parent) : Command(page, parent) {
+}
+
+void Url::start(QStringList &argments) {
+ Q_UNUSED(argments);
+
+ QUrl humanUrl = page()->currentFrame()->url();
+ QByteArray encodedBytes = humanUrl.toEncoded();
+ QString urlString = QString(encodedBytes);
+ emit finished(new Response(true, urlString));
+}
+
12 src/Url.h
@@ -0,0 +1,12 @@
+#include "Command.h"
+
+class WebPage;
+
+class Url : public Command {
+ Q_OBJECT
+
+ public:
+ Url(WebPage *page, QObject *parent = 0);
+ virtual void start(QStringList &argments);
+};
+
24 src/Visit.cpp
@@ -0,0 +1,24 @@
+#include "Visit.h"
+#include "Command.h"
+#include "WebPage.h"
+
+Visit::Visit(WebPage *page, QObject *parent) : Command(page, parent) {
+ connect(page, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool)));
+}
+
+void Visit::start(QStringList &arguments) {
+ QUrl requestedUrl = QUrl(arguments[0]);
+ page()->currentFrame()->setUrl(QUrl(requestedUrl));
+ if(requestedUrl.hasFragment()) {
+ // workaround for https://bugs.webkit.org/show_bug.cgi?id=32723
+ page()->currentFrame()->setUrl(QUrl(requestedUrl));
+ }
+}
+
+void Visit::loadFinished(bool success) {
+ QString message;
+ if (!success)
+ message = page()->failureString();
+
+ emit finished(new Response(success, message));
+}
15 src/Visit.h
@@ -0,0 +1,15 @@
+#include "Command.h"
+
+class WebPage;
+
+class Visit : public Command {
+ Q_OBJECT
+
+ public:
+ Visit(WebPage *page, QObject *parent = 0);
+ virtual void start(QStringList &arguments);
+
+ private slots:
+ void loadFinished(bool success);
+};
+
154 src/WebPage.cpp
@@ -0,0 +1,154 @@
+#include "WebPage.h"
+#include "JavascriptInvocation.h"
+#include "NetworkAccessManager.h"
+#include <QResource>
+#include <iostream>
+
+WebPage::WebPage(QObject *parent) : QWebPage(parent) {
+ QResource javascript(":/capybara.js");
+ if (javascript.isCompressed()) {
+ QByteArray uncompressedBytes(qUncompress(javascript.data(), javascript.size()));
+ m_capybaraJavascript = QString(uncompressedBytes);
+ } else {
+ char * javascriptString = new char[javascript.size() + 1];
+ strcpy(javascriptString, (const char *)javascript.data());
+ javascriptString[javascript.size()] = 0;
+ m_capybaraJavascript = javascriptString;
+ }
+ m_loading = false;
+
+ this->setNetworkAccessManager(new NetworkAccessManager());
+
+ connect(this, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
+ connect(this, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool)));
+ connect(this, SIGNAL(frameCreated(QWebFrame *)),
+ this, SLOT(frameCreated(QWebFrame *)));
+}
+
+QString WebPage::userAgentForUrl(const QUrl &url ) const {
+ if (!m_userAgent.isEmpty()) {
+ return m_userAgent;
+ } else {
+ return QWebPage::userAgentForUrl(url);
+ }
+}
+
+void WebPage::setUserAgent(QString userAgent) {
+ m_userAgent = userAgent;
+}
+
+void WebPage::frameCreated(QWebFrame * frame) {
+ connect(frame, SIGNAL(javaScriptWindowObjectCleared()),
+ this, SLOT(injectJavascriptHelpers()));
+}
+
+void WebPage::injectJavascriptHelpers() {
+ QWebFrame* frame = qobject_cast<QWebFrame *>(QObject::sender());
+ frame->evaluateJavaScript(m_capybaraJavascript);
+}
+
+bool WebPage::shouldInterruptJavaScript() {
+ return false;
+}
+
+QVariant WebPage::invokeCapybaraFunction(const char *name, QStringList &arguments) {
+ QString qname(name);
+ QString objectName("CapybaraInvocation");
+ JavascriptInvocation invocation(qname, arguments);
+ currentFrame()->addToJavaScriptWindowObject(objectName, &invocation);
+ QString javascript = QString("Capybara.invoke()");
+ return currentFrame()->evaluateJavaScript(javascript);
+}
+
+QVariant WebPage::invokeCapybaraFunction(QString &name, QStringList &arguments) {
+ return invokeCapybaraFunction(name.toAscii().data(), arguments);
+}
+
+void WebPage::javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID) {
+ if (!sourceID.isEmpty())
+ std::cout << qPrintable(sourceID) << ":" << lineNumber << " ";
+ std::cout << qPrintable(message) << std::endl;
+}
+
+void WebPage::javaScriptAlert(QWebFrame *frame, const QString &message) {
+ Q_UNUSED(frame);
+ std::cout << "ALERT: " << qPrintable(message) << std::endl;
+}
+
+bool WebPage::javaScriptConfirm(QWebFrame *frame, const QString &message) {
+ Q_UNUSED(frame);
+ Q_UNUSED(message);
+ return true;
+}
+
+bool WebPage::javaScriptPrompt(QWebFrame *frame, const QString &message, const QString &defaultValue, QString *result) {
+ Q_UNUSED(frame)
+ Q_UNUSED(message)
+ Q_UNUSED(defaultValue)
+ Q_UNUSED(result)
+ return false;
+}
+
+void WebPage::loadStarted() {
+ m_loading = true;
+}
+
+void WebPage::loadFinished(bool success) {
+ Q_UNUSED(success);
+ m_loading = false;
+}
+
+bool WebPage::isLoading() const {
+ return m_loading;
+}
+
+QString WebPage::failureString() {
+ return QString("Unable to load URL: ") + currentFrame()->requestedUrl().toString();
+}
+
+bool WebPage::render(const QString &fileName) {
+ QFileInfo fileInfo(fileName);
+ QDir dir;
+ dir.mkpath(fileInfo.absolutePath());
+
+ QSize viewportSize = this->viewportSize();
+ QSize pageSize = this->mainFrame()->contentsSize();
+ if (pageSize.isEmpty()) {
+ return false;
+ }
+
+ QImage buffer(pageSize, QImage::Format_ARGB32);
+ buffer.fill(qRgba(255, 255, 255, 0));
+
+ QPainter p(&buffer);
+ p.setRenderHint( QPainter::Antialiasing, true);
+ p.setRenderHint( QPainter::TextAntialiasing, true);
+ p.setRenderHint( QPainter::SmoothPixmapTransform, true);
+
+ this->setViewportSize(pageSize);
+ this->mainFrame()->render(&p);
+ p.end();
+ this->setViewportSize(viewportSize);
+
+ return buffer.save(fileName);
+}
+
+QString WebPage::chooseFile(QWebFrame *parentFrame, const QString &suggestedFile) {
+ Q_UNUSED(parentFrame);
+ Q_UNUSED(suggestedFile);
+
+ return getLastAttachedFileName();
+}
+
+bool WebPage::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) {
+ if (extension == ChooseMultipleFilesExtension) {
+ QStringList names = QStringList() << getLastAttachedFileName();
+ static_cast<ChooseMultipleFilesExtensionReturn*>(output)->fileNames = names;
+ return true;
+ }
+ return false;
+}
+
+QString WebPage::getLastAttachedFileName() {
+ return currentFrame()->evaluateJavaScript(QString("Capybara.lastAttachedFile")).toString();
+}
37 src/WebPage.h
@@ -0,0 +1,37 @@
+#include <QtWebKit>
+
+class WebPage : public QWebPage {
+ Q_OBJECT
+
+ public:
+ WebPage(QObject *parent = 0);
+ QVariant invokeCapybaraFunction(const char *name, QStringList &arguments);
+ QVariant invokeCapybaraFunction(QString &name, QStringList &arguments);
+ QString failureString();
+ QString userAgentForUrl(const QUrl &url ) const;
+ void setUserAgent(QString userAgent);
+ bool render(const QString &fileName);
+ virtual bool extension (Extension extension, const ExtensionOption *option=0, ExtensionReturn *output=0);
+
+ public slots:
+ bool shouldInterruptJavaScript();
+ void injectJavascriptHelpers();
+ void loadStarted();
+ void loadFinished(bool);
+ bool isLoading() const;
+ void frameCreated(QWebFrame *);
+
+ protected:
+ virtual void javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID);
+ virtual void javaScriptAlert(QWebFrame *frame, const QString &message);
+ virtual bool javaScriptConfirm(QWebFrame *frame, const QString &message);
+ virtual bool javaScriptPrompt(QWebFrame *frame, const QString &message, const QString &defaultValue, QString *result);
+ virtual QString chooseFile(QWebFrame * parentFrame, const QString &suggestedFile);
+
+ private:
+ QString m_capybaraJavascript;
+ QString m_userAgent;
+ bool m_loading;
+ QString getLastAttachedFileName();
+};
+
11 src/body.cpp
@@ -0,0 +1,11 @@
+#include "Body.h"
+#include "WebPage.h"
+
+Body::Body(WebPage *page, QObject *parent) : Command(page, parent) {
+}
+
+void Body::start(QStringList &arguments) {
+ Q_UNUSED(arguments);
+ QString result = page()->currentFrame()->toHtml();
+ emit finished(new Response(true, result));
+}
194 src/capybara.js
@@ -0,0 +1,194 @@
+Capybara = {
+ nextIndex: 0,
+ nodes: {},
+ lastAttachedFile: "",
+
+ invoke: function () {
+ return this[CapybaraInvocation.functionName].apply(this, CapybaraInvocation.arguments);
+ },
+
+ find: function (xpath) {
+ return this.findRelativeTo(document, xpath);
+ },
+
+ findWithin: function (index, xpath) {
+ return this.findRelativeTo(this.nodes[index], xpath);
+ },
+
+ findRelativeTo: function (reference, xpath) {
+ var iterator = document.evaluate(xpath, reference, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
+ var node;
+ var results = [];
+ while (node = iterator.iterateNext()) {
+ this.nextIndex++;
+ this.nodes[this.nextIndex] = node;
+ results.push(this.nextIndex);
+ }
+ return results.join(",");
+ },
+
+ text: function (index) {
+ var node = this.nodes[index];
+ var type = (node.type || node.tagName).toLowerCase();
+ if (type == "textarea") {
+ return node.innerHTML;
+ } else {
+ return node.innerText;
+ }
+ },
+
+ attribute: function (index, name) {
+ switch(name) {
+ case 'checked':
+ return this.nodes[index].checked;
+ break;
+
+ case 'disabled':
+ return this.nodes[index].disabled;
+ break;
+
+ default:
+ return this.nodes[index].getAttribute(name);
+ }
+ },
+
+ tagName: function(index) {
+ return this.nodes[index].tagName.toLowerCase();
+ },
+
+ click: function (index) {
+ var clickEvent = document.createEvent('MouseEvents');
+ clickEvent.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ this.nodes[index].dispatchEvent(clickEvent);
+ },
+
+ trigger: function (index, eventName) {
+ var eventObject = document.createEvent("HTMLEvents");
+ eventObject.initEvent(eventName, true, true);
+ this.nodes[index].dispatchEvent(eventObject);
+ },
+
+ keypress: function(index, altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {
+ var eventObject = document.createEvent("Events");
+ eventObject.initEvent('keypress', true, true);
+ eventObject.window = window;
+ eventObject.altKey = altKey;
+ eventObject.ctrlKey = ctrlKey;
+ eventObject.shiftKey = shiftKey;
+ eventObject.metaKey = metaKey;
+ eventObject.keyCode = keyCode;
+ eventObject.charCode = charCode;
+ this.nodes[index].dispatchEvent(eventObject);
+ },
+
+ visible: function (index) {
+ var element = this.nodes[index];
+ while (element) {
+ if (element.ownerDocument.defaultView.getComputedStyle(element, null).getPropertyValue("display") == 'none')
+ return false;
+ element = element.parentElement;
+ }
+ return true;
+ },
+
+ selected: function (index) {
+ return this.nodes[index].selected;
+ },
+
+ value: function(index) {
+ return this.nodes[index].value;
+ },
+
+ set: function(index, value) {
+ var node = this.nodes[index];
+ var type = (node.type || node.tagName).toLowerCase();
+ if (type == "text" || type == "textarea" || type == "password") {
+ this.trigger(index, "focus");
+ node.value = "";
+ var length = this.attribute(index, "maxlength") || value.length;
+ for(var strindex = 0; strindex < length; strindex++) {
+ node.value += value[strindex];
+ this.trigger(index, "keydown");
+ this.keypress(index, false, false, false, false, 0, value[strindex]);
+ this.trigger(index, "keyup");
+ }
+ this.trigger(index, "change");
+ this.trigger(index, "blur");
+ } else if(type == "checkbox" || type == "radio") {
+ node.checked = (value == "true");
+ this.trigger(index, "click");
+ this.trigger(index, "change");
+ } else if(type == "file") {
+ this.lastAttachedFile = value;
+ this.trigger(index, "click");
+ } else {
+ node.value = value;
+ }
+ },
+
+ selectOption: function(index) {
+ this.nodes[index].selected = true;
+ this.nodes[index].setAttribute("selected", "selected");
+ this.trigger(index, "change");
+ },
+
+ unselectOption: function(index) {
+ this.nodes[index].selected = false;
+ this.nodes[index].removeAttribute("selected");
+ this.trigger(index, "change");
+ },
+
+ centerPostion: function(element) {
+ this.reflow(element);
+ var rect = element.getBoundingClientRect();
+ var position = {
+ x: rect.width / 2,
+ y: rect.height / 2
+ };
+ do {
+ position.x += element.offsetLeft;
+ position.y += element.offsetTop;
+ } while ((element = element.offsetParent));
+ position.x = Math.floor(position.x), position.y = Math.floor(position.y);
+
+ return position;
+ },
+
+ reflow: function(element, force) {
+ if (force || element.offsetWidth === 0) {
+ var prop, oldStyle = {}, newStyle = {position: "absolute", visibility : "hidden", display: "block" };
+ for (prop in newStyle) {
+ oldStyle[prop] = element.style[prop];
+ element.style[prop] = newStyle[prop];
+ }
+ element.offsetWidth, element.offsetHeight; // force reflow
+ for (prop in oldStyle)
+ element.style[prop] = oldStyle[prop];
+ }
+ },
+
+ dragTo: function (index, targetIndex) {
+ var element = this.nodes[index], target = this.nodes[targetIndex];
+ var position = this.centerPostion(element);
+ var options = {
+ clientX: position.x,
+ clientY: position.y
+ };
+ var mouseTrigger = function(eventName, options) {
+ var eventObject = document.createEvent("MouseEvents");
+ eventObject.initMouseEvent(eventName, true, true, window, 0, 0, 0, options.clientX || 0, options.clientY || 0, false, false, false, false, 0, null);
+ element.dispatchEvent(eventObject);
+ }
+ mouseTrigger('mousedown', options);
+ options.clientX += 1, options.clientY += 1;
+ mouseTrigger('mousemove', options);
+
+ position = this.centerPostion(target), options = {
+ clientX: position.x,
+ clientY: position.y
+ };
+ mouseTrigger('mousemove', options);
+ mouseTrigger('mouseup', options);
+ }
+};
+
17 src/find_command.h
@@ -0,0 +1,17 @@
+#define CHECK_COMMAND(expectedName) \
+ if (strcmp(#expectedName, name) == 0) { \
+ return new expectedName(m_page, this); \
+ }
+
+CHECK_COMMAND(Visit)
+CHECK_COMMAND(Find)
+CHECK_COMMAND(Reset)
+CHECK_COMMAND(Node)
+CHECK_COMMAND(Url)
+CHECK_COMMAND(Source)
+CHECK_COMMAND(Evaluate)
+CHECK_COMMAND(Execute)
+CHECK_COMMAND(FrameFocus)
+CHECK_COMMAND(Header)
+CHECK_COMMAND(Render)
+CHECK_COMMAND(Body)
21 src/main.cpp
@@ -0,0 +1,21 @@
+#include "Server.h"
+#include <QtGui>
+#include <iostream>
+
+int main(int argc, char **argv) {
+ QApplication app(argc, argv);
+ app.setApplicationName("capybara-webkit");
+ app.setOrganizationName("thoughtbot, inc");
+ app.setOrganizationDomain("thoughtbot.com");
+
+ Server server;
+
+ if (server.start()) {
+ std::cout << "Capybara-webkit server started, listening on port: " << server.server_port() << std::endl;
+ return app.exec();
+ } else {
+ std::cerr << "Couldn't start capybara-webkit server" << std::endl;
+ return 1;
+ }
+}
+
10 src/webkit_server.pro
@@ -0,0 +1,10 @@
+TEMPLATE = app
+TARGET = webkit_server
+DESTDIR = .
+HEADERS = WebPage.h Server.h Connection.h Command.h Visit.h Find.h Reset.h Node.h JavascriptInvocation.h Url.h Source.h Evaluate.h Execute.h FrameFocus.h Response.h NetworkAccessManager.h Header.h Render.h body.h
+SOURCES = main.cpp WebPage.cpp Server.cpp Connection.cpp Command.cpp Visit.cpp Find.cpp Reset.cpp Node.cpp JavascriptInvocation.cpp Url.cpp Source.cpp Evaluate.cpp Execute.cpp FrameFocus.cpp Response.cpp NetworkAccessManager.cpp Header.cpp Render.cpp body.cpp
+RESOURCES = webkit_server.qrc
+QT += network webkit
+CONFIG += console
+CONFIG -= app_bundle
+
5 src/webkit_server.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>capybara.js</file>
+ </qresource>
+</RCC>
3  webkit_server.pro
@@ -0,0 +1,3 @@
+CONFIG += ordered
+SUBDIRS += src/webkit_server.pro
+TEMPLATE = subdirs
Please sign in to comment.
Something went wrong with that request. Please try again.