├── .gitattributes ├── .gitconfig ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── build.sh ├── debian ├── changelog ├── compat ├── control ├── copyright ├── lintian-overrides ├── rules └── source │ ├── format │ └── options ├── setup.py ├── src ├── Authenticate.cpp ├── Authenticate.h ├── Body.cpp ├── Body.h ├── ClearCookies.cpp ├── ClearCookies.h ├── ClearPromptText.cpp ├── ClearPromptText.h ├── Command.cpp ├── Command.h ├── CommandFactory.cpp ├── CommandFactory.h ├── CommandParser.cpp ├── CommandParser.h ├── Connection.cpp ├── Connection.h ├── ConsoleMessages.cpp ├── ConsoleMessages.h ├── CurrentUrl.cpp ├── CurrentUrl.h ├── EnableLogging.cpp ├── EnableLogging.h ├── ErrorMessage.cpp ├── ErrorMessage.h ├── Evaluate.cpp ├── Evaluate.h ├── Execute.cpp ├── Execute.h ├── FindCss.cpp ├── FindCss.h ├── FindXpath.cpp ├── FindXpath.h ├── FrameFocus.cpp ├── FrameFocus.h ├── GetCookies.cpp ├── GetCookies.h ├── GetTimeout.cpp ├── GetTimeout.h ├── GetWindowHandle.cpp ├── GetWindowHandle.h ├── GetWindowHandles.cpp ├── GetWindowHandles.h ├── Header.cpp ├── Header.h ├── Headers.cpp ├── Headers.h ├── IgnoreDebugOutput.cpp ├── IgnoreDebugOutput.h ├── IgnoreSslErrors.cpp ├── IgnoreSslErrors.h ├── InvocationResult.cpp ├── InvocationResult.h ├── JavascriptAlertMessages.cpp ├── JavascriptAlertMessages.h ├── JavascriptCommand.cpp ├── JavascriptCommand.h ├── JavascriptConfirmMessages.cpp ├── JavascriptConfirmMessages.h ├── JavascriptInvocation.cpp ├── JavascriptInvocation.h ├── JavascriptPromptMessages.cpp ├── JavascriptPromptMessages.h ├── JsonSerializer.cpp ├── JsonSerializer.h ├── NetworkAccessManager.cpp ├── NetworkAccessManager.h ├── NetworkCookieJar.cpp ├── NetworkCookieJar.h ├── NetworkReplyProxy.cpp ├── NetworkReplyProxy.h ├── NoOpReply.cpp ├── NoOpReply.h ├── Node.cpp ├── Node.h ├── NullCommand.cpp ├── NullCommand.h ├── PageLoadingCommand.cpp ├── PageLoadingCommand.h ├── Render.cpp ├── Render.h ├── Reset.cpp ├── Reset.h ├── ResizeWindow.cpp ├── ResizeWindow.h ├── Response.cpp ├── Response.h ├── Server.cpp ├── Server.h ├── SetAttribute.cpp ├── SetAttribute.h ├── SetConfirmAction.cpp ├── SetConfirmAction.h ├── SetCookie.cpp ├── SetCookie.h ├── SetHtml.cpp ├── SetHtml.h ├── SetPromptAction.cpp ├── SetPromptAction.h ├── SetPromptText.cpp ├── SetPromptText.h ├── SetProxy.cpp ├── SetProxy.h ├── SetSkipImageLoading.cpp ├── SetSkipImageLoading.h ├── SetTimeout.cpp ├── SetTimeout.h ├── SetUrlBlacklist.cpp ├── SetUrlBlacklist.h ├── SocketCommand.cpp ├── SocketCommand.h ├── Source.cpp ├── Source.h ├── Status.cpp ├── Status.h ├── TimeoutCommand.cpp ├── TimeoutCommand.h ├── Title.cpp ├── Title.h ├── UnsupportedContentHandler.cpp ├── UnsupportedContentHandler.h ├── Version.cpp ├── Version.h ├── Visit.cpp ├── Visit.h ├── WebPage.cpp ├── WebPage.h ├── WebPageManager.cpp ├── WebPageManager.h ├── WindowFocus.cpp ├── WindowFocus.h ├── capybara.js ├── find_command.h ├── main.cpp ├── pointer.png ├── stable.h ├── webkit_server.pro └── webkit_server.qrc ├── webkit_server.pro └── webkit_server.py /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [core] 2 | whitespace = trailing-space,space-before-tab 3 | [apply] 4 | whitespace = fix 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.o 3 | *.moc 4 | *.pyc 5 | moc_*.cpp 6 | Makefile* 7 | qrc_*.cpp 8 | /src/webkit_server 9 | /webkit_server 10 | /MANIFEST 11 | /dist 12 | /build 13 | /src/build 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2012 thoughtbot, inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE README.md webkit_server.pro 2 | recursive-include src *.cpp *.js *.h *.qrc *.pro *.png 3 | recursive-exclude src moc_* qrc_* 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **NOTE: This package is not actively maintained. It uses QtWebkit, which is end-of-life and probably doesn't get security fixes backported. Consider using a similar package like Spynner instead.** 2 | 3 | # Overview 4 | 5 | **Author:** Niklas Baumstark 6 | 7 | A standalone version of the Webkit server included in [capybara-webkit][1]. 8 | It includes a slim Python wrapper and the following improvements over the 9 | original version from thoughtbot: 10 | 11 | * `Wait` command to wait for the current page to load 12 | * `SetAttribute` command to [configure certain `QWebkit` settings][2] 13 | * `SetHtml` command to [load custom HTML][3] into the browser (e.g. to 14 | execute scripts on web pages scraped by a static scraper) 15 | * `SetViewportSize` command to set the viewport size of the in-memory browser 16 | 17 | If you are interested in web scraping using this server, have a look at [dryscrape][4]. 18 | 19 | # Building and Installing 20 | 21 | To install the Python binding (this also builds the server and places it into 22 | Python's `site-package` directory): 23 | 24 | sudo python setup.py install 25 | 26 | If you don't need the Python bindings, you can also use the supplied `build.sh` 27 | shellscript to build the server only. 28 | 29 | ### A word about Qt 5.6 30 | 31 | The 5.6 version of Qt removes the Qt WebKit module in favor of the new module Qt WebEngine. So far webkit-server has not been ported to WebEngine (and likely won't be in the near future), so Qt <= 5.5 is a requirement. 32 | 33 | # Contact, Bugs, Contributions 34 | 35 | If you have any problems with this software, don't hesitate to open an 36 | issue on [Github](https://github.com/niklasb/webkit-server) or open a pull 37 | request or write a mail to **niklas 38 | baumstark at Gmail**. 39 | 40 | # License 41 | 42 | This software is based on [capybara-webkit][1]. 43 | capybara-webkit is Copyright (c) 2011 thoughtbot, inc. It is free software, and 44 | may be redistributed under the terms specified in the LICENSE file. 45 | 46 | [1]: https://github.com/thoughtbot/capybara-webkit 47 | [2]: https://github.com/thoughtbot/capybara-webkit/pull/171 48 | [3]: https://github.com/thoughtbot/capybara-webkit/pull/170 49 | [4]: https://github.com/niklasb/dryscrape 50 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | QMAKE_ARGS= 4 | if [ "$(uname)" == "Darwin" ]; then 5 | # ensure Makefile is generated rather than XCode project 6 | QMAKE_ARGS='-spec macx-g++' 7 | fi 8 | 9 | qmake $QMAKE_ARGS && make -j 4 $MAKEFLAGS || exit $? 10 | cp src/webkit_server webkit_server 11 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | webkit-server (1.0-1) unstable; urgency=low 2 | 3 | * Initial import 4 | 5 | -- Niklas Baumstark Wed, 23 Sep 2015 10:17:33 +0000 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: webkit-server 2 | Maintainer: Niklas Baumstark 3 | Section: python 4 | Priority: optional 5 | Build-Depends: python-all (>= 2.6.6-3), debhelper (>= 7), qt5-default, libqt5webkit5-dev 6 | Standards-Version: 3.9.1 7 | 8 | Package: python-webkit-server 9 | Architecture: any 10 | XB-Python-Version: ${python:Versions} 11 | Depends: ${misc:Depends}, ${shlibs:Depends}, ${python:Depends} 12 | Provides: ${python:Provides} 13 | Description: a Webkit-based, headless web client 14 | A standalone version of the Webkit server included in capybara-webkit. 15 | . 16 | It includes a slim Python wrapper and the following improvements over 17 | the original version from thoughtbot: 18 | . 19 | * Wait command to wait for the current page to load 20 | * SetAttribute command to configure certain QWebkit settings 21 | * SetHtml command to load custom HTML into the browser 22 | (e.g. to execute scripts on web pages scraped by a static scraper) 23 | * SetViewportSize command to set the viewport size of the in-memory browser 24 | . 25 | If you are interested in web scraping using this server, 26 | have a look at dryscrape (https://github.com/niklasb/dryscrape). 27 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: webkit-server 3 | Source: https://github.com/niklasb/webkit-server 4 | 5 | Files: * 6 | Copyright: Copyright (c) 2010-2012 thoughtbot, inc. 7 | License: MIT 8 | For details see http://opensource.org/licenses/MIT. 9 | -------------------------------------------------------------------------------- /debian/lintian-overrides: -------------------------------------------------------------------------------- 1 | python-webkit-server binary: description-synopsis-starts-with-article 2 | python-webkit-server binary: new-package-should-close-itp-bug 3 | 4 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ --with python2 --buildsystem=python_distutils 5 | 6 | 7 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/source/options: -------------------------------------------------------------------------------- 1 | extend-diff-ignore="\.egg-info" -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup, Command 2 | from distutils.command.build import build as _build 3 | import os, sys 4 | import shutil 5 | import platform 6 | 7 | class build_server(_build): 8 | description = 'custom build command' 9 | sub_commands = [] 10 | 11 | def initialize_options(self): 12 | _build.initialize_options(self) 13 | self.cwd = None 14 | def finalize_options(self): 15 | _build.finalize_options(self) 16 | self.cwd = os.getcwd() 17 | def run(self): 18 | if os.environ.get('READTHEDOCS', None) == 'True': 19 | # won't build on readthedocs.org 20 | return 21 | assert os.getcwd() == self.cwd, 'Must be in package root.' 22 | # append any platform specific qmake args to this list 23 | args=[] 24 | if platform.system() == 'Darwin': 25 | # ensure a Makefile is generated rather than an XCode project on OSX 26 | args += ['-spec', 'macx-g++'] 27 | os.system('qmake ' + ' '.join(args) + ' && make') 28 | try: 29 | os.remove(os.path.join(self.build_purelib, 'webkit_server')) 30 | except: pass 31 | try: 32 | os.remove(os.path.join(self.build_platlib, 'webkit_server')) 33 | except: pass 34 | try: 35 | os.makedirs(self.build_platlib) 36 | except: pass 37 | try: 38 | os.makedirs(self.build_purelib) 39 | except: pass 40 | shutil.copy('src/webkit_server', self.build_purelib) 41 | shutil.copy('src/webkit_server', self.build_platlib) 42 | 43 | setup(name='webkit-server', 44 | version='1.0', 45 | description='a Webkit-based, headless web client', 46 | author='Niklas Baumstark', 47 | author_email='niklas.baumstark@gmail.com', 48 | license='MIT', 49 | url='https://github.com/niklasb/webkit-server', 50 | py_modules=['webkit_server'], 51 | cmdclass={ 52 | 'build': build_server, 53 | }) 54 | -------------------------------------------------------------------------------- /src/Authenticate.cpp: -------------------------------------------------------------------------------- 1 | #include "Authenticate.h" 2 | #include "WebPage.h" 3 | #include "NetworkAccessManager.h" 4 | #include "WebPageManager.h" 5 | 6 | Authenticate::Authenticate(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void Authenticate::start() { 10 | QString username = arguments()[0]; 11 | QString password = arguments()[1]; 12 | 13 | NetworkAccessManager* networkAccessManager = manager()->networkAccessManager(); 14 | networkAccessManager->setUserName(username); 15 | networkAccessManager->setPassword(password); 16 | 17 | finish(true); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/Authenticate.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class WebPage; 4 | 5 | class Authenticate : public SocketCommand { 6 | Q_OBJECT 7 | 8 | public: 9 | Authenticate(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); 10 | virtual void start(); 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /src/Body.cpp: -------------------------------------------------------------------------------- 1 | #include "Body.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | 5 | Body::Body(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 6 | } 7 | 8 | void Body::start() { 9 | if (page()->contentType().contains("html")) 10 | finish(true, page()->currentFrame()->toHtml()); 11 | else 12 | finish(true, page()->body()); 13 | } 14 | -------------------------------------------------------------------------------- /src/Body.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class Body : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | Body(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/ClearCookies.cpp: -------------------------------------------------------------------------------- 1 | #include "ClearCookies.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "NetworkCookieJar.h" 5 | #include 6 | 7 | ClearCookies::ClearCookies(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} 8 | 9 | void ClearCookies::start() 10 | { 11 | manager()->cookieJar()->clearCookies(); 12 | finish(true); 13 | } 14 | -------------------------------------------------------------------------------- /src/ClearCookies.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class ClearCookies : public SocketCommand { 4 | Q_OBJECT; 5 | 6 | public: 7 | ClearCookies(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/ClearPromptText.cpp: -------------------------------------------------------------------------------- 1 | #include "ClearPromptText.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | 5 | ClearPromptText::ClearPromptText(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} 6 | 7 | void ClearPromptText::start() 8 | { 9 | page()->setPromptText(QString()); 10 | finish(true); 11 | } 12 | -------------------------------------------------------------------------------- /src/ClearPromptText.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class ClearPromptText : public SocketCommand { 4 | Q_OBJECT; 5 | 6 | public: 7 | ClearPromptText(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/Command.cpp: -------------------------------------------------------------------------------- 1 | #include "Command.h" 2 | #include "ErrorMessage.h" 3 | 4 | Command::Command(QObject *parent) : QObject(parent) { 5 | } 6 | 7 | QString Command::toString() const { 8 | return metaObject()->className(); 9 | } 10 | 11 | void Command::finish(bool success) { 12 | emit finished(new Response(success, this)); 13 | } 14 | 15 | void Command::finish(bool success, QString message) { 16 | emit finished(new Response(success, message, this)); 17 | } 18 | 19 | void Command::finish(bool success, QByteArray message) { 20 | emit finished(new Response(success, message, this)); 21 | } 22 | 23 | void Command::finish(bool success, ErrorMessage *message) { 24 | emit finished(new Response(success, message, this)); 25 | } 26 | -------------------------------------------------------------------------------- /src/Command.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMAND_H 2 | #define COMMAND_H 3 | 4 | #include "Response.h" 5 | #include 6 | #include 7 | 8 | class ErrorMessage; 9 | 10 | class Command : public QObject { 11 | Q_OBJECT 12 | 13 | public: 14 | Command(QObject *parent = 0); 15 | virtual void start() = 0; 16 | virtual QString toString() const; 17 | 18 | protected: 19 | void finish(bool success); 20 | void finish(bool success, QString message); 21 | void finish(bool success, QByteArray message); 22 | void finish(bool success, ErrorMessage *message); 23 | 24 | signals: 25 | void finished(Response *response); 26 | }; 27 | 28 | #endif 29 | 30 | -------------------------------------------------------------------------------- /src/CommandFactory.cpp: -------------------------------------------------------------------------------- 1 | #include "CommandFactory.h" 2 | #include "NullCommand.h" 3 | #include "Visit.h" 4 | #include "FindXpath.h" 5 | #include "Reset.h" 6 | #include "Node.h" 7 | #include "Evaluate.h" 8 | #include "Execute.h" 9 | #include "FrameFocus.h" 10 | #include "Header.h" 11 | #include "Render.h" 12 | #include "Body.h" 13 | #include "Status.h" 14 | #include "Headers.h" 15 | #include "SetCookie.h" 16 | #include "ClearCookies.h" 17 | #include "GetCookies.h" 18 | #include "SetProxy.h" 19 | #include "ConsoleMessages.h" 20 | #include "CurrentUrl.h" 21 | #include "SetTimeout.h" 22 | #include "GetTimeout.h" 23 | #include "ResizeWindow.h" 24 | #include "IgnoreSslErrors.h" 25 | #include "SetSkipImageLoading.h" 26 | #include "WindowFocus.h" 27 | #include "GetWindowHandles.h" 28 | #include "GetWindowHandle.h" 29 | #include "WebPageManager.h" 30 | #include "Authenticate.h" 31 | #include "EnableLogging.h" 32 | #include "SetConfirmAction.h" 33 | #include "SetPromptAction.h" 34 | #include "SetPromptText.h" 35 | #include "ClearPromptText.h" 36 | #include "JavascriptAlertMessages.h" 37 | #include "JavascriptConfirmMessages.h" 38 | #include "JavascriptPromptMessages.h" 39 | #include "SetUrlBlacklist.h" 40 | #include "Version.h" 41 | #include "Title.h" 42 | #include "FindCss.h" 43 | #include "Source.h" 44 | #include "SetHtml.h" 45 | #include "SetAttribute.h" 46 | 47 | CommandFactory::CommandFactory(WebPageManager *manager, QObject *parent) : QObject(parent) { 48 | m_manager = manager; 49 | } 50 | 51 | Command *CommandFactory::createCommand(const char *name, QStringList &arguments) { 52 | #include "find_command.h" 53 | return new NullCommand(QString(name)); 54 | } 55 | -------------------------------------------------------------------------------- /src/CommandFactory.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class Command; 4 | class WebPage; 5 | class WebPageManager; 6 | 7 | class CommandFactory : public QObject { 8 | Q_OBJECT 9 | 10 | public: 11 | CommandFactory(WebPageManager *, QObject *parent = 0); 12 | Command *createCommand(const char *name, QStringList &arguments); 13 | 14 | private: 15 | WebPageManager *m_manager; 16 | }; 17 | 18 | -------------------------------------------------------------------------------- /src/CommandParser.cpp: -------------------------------------------------------------------------------- 1 | #include "CommandParser.h" 2 | #include "CommandFactory.h" 3 | #include "SocketCommand.h" 4 | 5 | #include 6 | 7 | CommandParser::CommandParser(QIODevice *device, CommandFactory *commandFactory, QObject *parent) : 8 | QObject(parent) { 9 | m_device = device; 10 | m_expectingDataSize = -1; 11 | m_commandFactory = commandFactory; 12 | connect(m_device, SIGNAL(readyRead()), this, SLOT(checkNext())); 13 | } 14 | 15 | void CommandParser::checkNext() { 16 | if (m_expectingDataSize == -1) { 17 | if (m_device->canReadLine()) { 18 | readLine(); 19 | checkNext(); 20 | } 21 | } else { 22 | if (m_device->bytesAvailable() >= m_expectingDataSize) { 23 | readDataBlock(); 24 | checkNext(); 25 | } 26 | } 27 | } 28 | 29 | void CommandParser::readLine() { 30 | char buffer[128]; 31 | qint64 lineLength = m_device->readLine(buffer, 128); 32 | if (lineLength != -1) { 33 | buffer[lineLength - 1] = 0; 34 | processNext(buffer); 35 | } 36 | } 37 | 38 | void CommandParser::readDataBlock() { 39 | char *buffer = new char[m_expectingDataSize + 1]; 40 | m_device->read(buffer, m_expectingDataSize); 41 | buffer[m_expectingDataSize] = 0; 42 | processNext(buffer); 43 | m_expectingDataSize = -1; 44 | delete[] buffer; 45 | } 46 | 47 | void CommandParser::processNext(const char *data) { 48 | if (m_commandName.isNull()) { 49 | m_commandName = data; 50 | m_argumentsExpected = -1; 51 | } else { 52 | processArgument(data); 53 | } 54 | } 55 | 56 | void CommandParser::processArgument(const char *data) { 57 | if (m_argumentsExpected == -1) { 58 | m_argumentsExpected = QString(data).toInt(); 59 | } else if (m_expectingDataSize == -1) { 60 | m_expectingDataSize = QString(data).toInt(); 61 | } else { 62 | m_arguments.append(QString::fromUtf8(data)); 63 | } 64 | 65 | if (m_arguments.length() == m_argumentsExpected) { 66 | Command *command = m_commandFactory->createCommand(m_commandName.toLatin1().constData(), m_arguments); 67 | emit commandReady(command); 68 | reset(); 69 | } 70 | } 71 | 72 | void CommandParser::reset() { 73 | m_commandName = QString(); 74 | m_arguments.clear(); 75 | m_argumentsExpected = -1; 76 | } 77 | -------------------------------------------------------------------------------- /src/CommandParser.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class QIODevice; 5 | class CommandFactory; 6 | class Command; 7 | 8 | class CommandParser : public QObject { 9 | Q_OBJECT 10 | 11 | public: 12 | CommandParser(QIODevice *device, CommandFactory *commandFactory, QObject *parent = 0); 13 | 14 | public slots: 15 | void checkNext(); 16 | 17 | signals: 18 | void commandReady(Command *command); 19 | 20 | private: 21 | void readLine(); 22 | void readDataBlock(); 23 | void processNext(const char *line); 24 | void processArgument(const char *data); 25 | void reset(); 26 | QIODevice *m_device; 27 | QString m_commandName; 28 | QStringList m_arguments; 29 | int m_argumentsExpected; 30 | int m_expectingDataSize; 31 | CommandFactory *m_commandFactory; 32 | }; 33 | 34 | -------------------------------------------------------------------------------- /src/Connection.cpp: -------------------------------------------------------------------------------- 1 | #include "Connection.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "CommandParser.h" 5 | #include "CommandFactory.h" 6 | #include "PageLoadingCommand.h" 7 | #include "TimeoutCommand.h" 8 | #include "SocketCommand.h" 9 | #include "ErrorMessage.h" 10 | 11 | #include 12 | 13 | Connection::Connection(QTcpSocket *socket, WebPageManager *manager, QObject *parent) : 14 | QObject(parent) { 15 | m_socket = socket; 16 | m_manager = manager; 17 | m_commandFactory = new CommandFactory(m_manager, this); 18 | m_commandParser = new CommandParser(socket, m_commandFactory, this); 19 | m_pageSuccess = true; 20 | connect(m_socket, SIGNAL(readyRead()), m_commandParser, SLOT(checkNext())); 21 | connect(m_commandParser, SIGNAL(commandReady(Command *)), this, SLOT(commandReady(Command *))); 22 | connect(m_manager, SIGNAL(pageFinished(bool)), this, SLOT(pendingLoadFinished(bool))); 23 | } 24 | 25 | void Connection::commandReady(Command *command) { 26 | m_manager->logger() << "Received" << command->toString(); 27 | startCommand(command); 28 | } 29 | 30 | void Connection::startCommand(Command *command) { 31 | if (m_pageSuccess) { 32 | command = new TimeoutCommand(new PageLoadingCommand(command, m_manager, this), m_manager, this); 33 | connect(command, SIGNAL(finished(Response *)), this, SLOT(finishCommand(Response *))); 34 | command->start(); 35 | } else { 36 | writePageLoadFailure(); 37 | } 38 | } 39 | 40 | void Connection::pendingLoadFinished(bool success) { 41 | m_pageSuccess = m_pageSuccess && success; 42 | } 43 | 44 | void Connection::writePageLoadFailure() { 45 | m_pageSuccess = true; 46 | QString message = currentPage()->failureString(); 47 | Response response(false, new ErrorMessage(message)); 48 | writeResponse(&response); 49 | } 50 | 51 | void Connection::finishCommand(Response *response) { 52 | m_pageSuccess = true; 53 | writeResponse(response); 54 | sender()->deleteLater(); 55 | } 56 | 57 | void Connection::writeResponse(Response *response) { 58 | if (response->isSuccess()) 59 | m_socket->write("ok\n"); 60 | else 61 | m_socket->write("failure\n"); 62 | 63 | m_manager->logger() << "Wrote response" << response->isSuccess() << response->message(); 64 | 65 | QByteArray messageUtf8 = response->message(); 66 | QString messageLength = QString::number(messageUtf8.size()) + "\n"; 67 | m_socket->write(messageLength.toLatin1()); 68 | m_socket->write(messageUtf8); 69 | } 70 | 71 | WebPage *Connection::currentPage() { 72 | return m_manager->currentPage(); 73 | } 74 | -------------------------------------------------------------------------------- /src/Connection.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class QTcpSocket; 5 | class WebPage; 6 | class Command; 7 | class Response; 8 | class CommandParser; 9 | class CommandFactory; 10 | class PageLoadingCommand; 11 | class WebPageManager; 12 | 13 | class Connection : public QObject { 14 | Q_OBJECT 15 | 16 | public: 17 | Connection(QTcpSocket *socket, WebPageManager *manager, QObject *parent = 0); 18 | 19 | public slots: 20 | void commandReady(Command *command); 21 | void finishCommand(Response *response); 22 | void pendingLoadFinished(bool success); 23 | 24 | private: 25 | void startCommand(Command *); 26 | void writeResponse(Response *response); 27 | void writePageLoadFailure(); 28 | 29 | QTcpSocket *m_socket; 30 | WebPageManager *m_manager; 31 | CommandParser *m_commandParser; 32 | CommandFactory *m_commandFactory; 33 | bool m_pageSuccess; 34 | WebPage *currentPage(); 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /src/ConsoleMessages.cpp: -------------------------------------------------------------------------------- 1 | #include "ConsoleMessages.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "JsonSerializer.h" 5 | 6 | ConsoleMessages::ConsoleMessages(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void ConsoleMessages::start() { 10 | JsonSerializer serializer; 11 | QByteArray json = serializer.serialize(page()->consoleMessages()); 12 | finish(true, json); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/ConsoleMessages.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class ConsoleMessages : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | ConsoleMessages(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/CurrentUrl.cpp: -------------------------------------------------------------------------------- 1 | #include "CurrentUrl.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | 5 | CurrentUrl::CurrentUrl(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 6 | } 7 | 8 | void CurrentUrl::start() { 9 | QStringList arguments; 10 | QVariant result = page()->currentFrame()->evaluateJavaScript("window.location.toString()"); 11 | QString url = result.toString(); 12 | finish(true, url); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/CurrentUrl.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class CurrentUrl : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | CurrentUrl(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/EnableLogging.cpp: -------------------------------------------------------------------------------- 1 | #include "EnableLogging.h" 2 | #include "WebPageManager.h" 3 | 4 | EnableLogging::EnableLogging(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 5 | } 6 | 7 | void EnableLogging::start() { 8 | manager()->enableLogging(); 9 | finish(true); 10 | } 11 | -------------------------------------------------------------------------------- /src/EnableLogging.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class WebPageManager; 4 | 5 | class EnableLogging : public SocketCommand { 6 | Q_OBJECT 7 | 8 | public: 9 | EnableLogging(WebPageManager *, QStringList &arguments, QObject *parent = 0); 10 | virtual void start(); 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /src/ErrorMessage.cpp: -------------------------------------------------------------------------------- 1 | #include "ErrorMessage.h" 2 | #include "JsonSerializer.h" 3 | 4 | ErrorMessage::ErrorMessage(QString message, QObject *parent) : QObject(parent) { 5 | m_message = message; 6 | } 7 | 8 | ErrorMessage::ErrorMessage(QString type, QString message, QObject *parent) : QObject(parent) { 9 | m_type = type; 10 | m_message = message; 11 | } 12 | 13 | QByteArray ErrorMessage::toString() { 14 | JsonSerializer serializer; 15 | 16 | QVariantMap map; 17 | 18 | if (m_type.isNull()) 19 | map["class"] = "InvalidResponseError"; 20 | else 21 | map["class"] = m_type; 22 | 23 | map["message"] = m_message; 24 | 25 | return serializer.serialize(map); 26 | } 27 | -------------------------------------------------------------------------------- /src/ErrorMessage.h: -------------------------------------------------------------------------------- 1 | #ifndef __ERROR_MESSAGE_H 2 | #define __ERROR_MESSAGE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class ErrorMessage : public QObject { 9 | Q_OBJECT 10 | 11 | public: 12 | ErrorMessage(QString message, QObject *parent = 0); 13 | ErrorMessage(QString type, QString message, QObject *parent = 0); 14 | QByteArray toString(); 15 | 16 | private: 17 | QString m_type; 18 | QString m_message; 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/Evaluate.cpp: -------------------------------------------------------------------------------- 1 | #include "Evaluate.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "JsonSerializer.h" 5 | #include 6 | 7 | Evaluate::Evaluate(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 8 | } 9 | 10 | void Evaluate::start() { 11 | QVariant result = page()->currentFrame()->evaluateJavaScript(arguments()[0]); 12 | JsonSerializer serializer; 13 | finish(true, serializer.serialize(result)); 14 | } 15 | -------------------------------------------------------------------------------- /src/Evaluate.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | #include 4 | 5 | class Evaluate : public SocketCommand { 6 | Q_OBJECT 7 | 8 | public: 9 | Evaluate(WebPageManager *, QStringList &arguments, QObject *parent = 0); 10 | virtual void start(); 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /src/Execute.cpp: -------------------------------------------------------------------------------- 1 | #include "Execute.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "ErrorMessage.h" 5 | 6 | Execute::Execute(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void Execute::start() { 10 | QString script = arguments()[0] + QString("; 'success'"); 11 | QVariant result = page()->currentFrame()->evaluateJavaScript(script); 12 | if (result.isValid()) { 13 | finish(true); 14 | } else { 15 | finish(false, new ErrorMessage("Javascript failed to execute")); 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/Execute.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class Execute : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | Execute(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/FindCss.cpp: -------------------------------------------------------------------------------- 1 | #include "FindCss.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "InvocationResult.h" 5 | 6 | FindCss::FindCss(WebPageManager *manager, QStringList &arguments, QObject *parent) : JavascriptCommand(manager, arguments, parent) { 7 | } 8 | 9 | void FindCss::start() { 10 | InvocationResult result = page()->invokeCapybaraFunction("findCss", true, arguments()); 11 | finish(&result); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/FindCss.h: -------------------------------------------------------------------------------- 1 | #include "JavascriptCommand.h" 2 | 3 | class FindCss : public JavascriptCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | FindCss(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/FindXpath.cpp: -------------------------------------------------------------------------------- 1 | #include "FindXpath.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "InvocationResult.h" 5 | 6 | FindXpath::FindXpath(WebPageManager *manager, QStringList &arguments, QObject *parent) : JavascriptCommand(manager, arguments, parent) { 7 | } 8 | 9 | void FindXpath::start() { 10 | InvocationResult result = page()->invokeCapybaraFunction("findXpath", true, arguments()); 11 | finish(&result); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/FindXpath.h: -------------------------------------------------------------------------------- 1 | #include "JavascriptCommand.h" 2 | 3 | class FindXpath : public JavascriptCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | FindXpath(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/FrameFocus.cpp: -------------------------------------------------------------------------------- 1 | #include "FrameFocus.h" 2 | #include "SocketCommand.h" 3 | #include "WebPage.h" 4 | #include "WebPageManager.h" 5 | #include "ErrorMessage.h" 6 | 7 | FrameFocus::FrameFocus(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 8 | } 9 | 10 | void FrameFocus::start() { 11 | findFrames(); 12 | switch(arguments().length()) { 13 | case 1: 14 | focusId(arguments()[0]); 15 | break; 16 | case 2: 17 | focusIndex(arguments()[1].toInt()); 18 | break; 19 | default: 20 | focusParent(); 21 | } 22 | } 23 | 24 | void FrameFocus::findFrames() { 25 | frames = page()->currentFrame()->childFrames(); 26 | } 27 | 28 | void FrameFocus::focusIndex(int index) { 29 | if (isFrameAtIndex(index)) { 30 | frames[index]->setFocus(); 31 | success(); 32 | } else { 33 | frameNotFound(); 34 | } 35 | } 36 | 37 | bool FrameFocus::isFrameAtIndex(int index) { 38 | return 0 <= index && index < frames.length(); 39 | } 40 | 41 | void FrameFocus::focusId(QString name) { 42 | for (int i = 0; i < frames.length(); i++) { 43 | if (frames[i]->frameName().compare(name) == 0) { 44 | frames[i]->setFocus(); 45 | success(); 46 | return; 47 | } 48 | } 49 | 50 | frameNotFound(); 51 | } 52 | 53 | void FrameFocus::focusParent() { 54 | if (page()->currentFrame()->parentFrame() == 0) { 55 | finish(false, new ErrorMessage("Already at parent frame.")); 56 | } else { 57 | page()->currentFrame()->parentFrame()->setFocus(); 58 | success(); 59 | } 60 | } 61 | 62 | void FrameFocus::frameNotFound() { 63 | finish(false, new ErrorMessage("Unable to locate frame.")); 64 | } 65 | 66 | void FrameFocus::success() { 67 | finish(true); 68 | } 69 | -------------------------------------------------------------------------------- /src/FrameFocus.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class QWebFrame; 4 | 5 | class FrameFocus : public SocketCommand { 6 | Q_OBJECT 7 | 8 | public: 9 | FrameFocus(WebPageManager *, QStringList &arguments, QObject *parent = 0); 10 | virtual void start(); 11 | 12 | private: 13 | void findFrames(); 14 | 15 | void focusParent(); 16 | 17 | void focusIndex(int index); 18 | bool isFrameAtIndex(int index); 19 | 20 | void focusId(QString id); 21 | 22 | void success(); 23 | void frameNotFound(); 24 | 25 | QList frames; 26 | }; 27 | 28 | -------------------------------------------------------------------------------- /src/GetCookies.cpp: -------------------------------------------------------------------------------- 1 | #include "GetCookies.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "NetworkCookieJar.h" 5 | 6 | GetCookies::GetCookies(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) 7 | { 8 | m_buffer = ""; 9 | } 10 | 11 | void GetCookies::start() 12 | { 13 | NetworkCookieJar *jar = manager()->cookieJar(); 14 | foreach (QNetworkCookie cookie, jar->getAllCookies()) { 15 | m_buffer.append(cookie.toRawForm()); 16 | m_buffer.append("\n"); 17 | } 18 | finish(true, m_buffer); 19 | } 20 | -------------------------------------------------------------------------------- /src/GetCookies.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class GetCookies : public SocketCommand { 4 | Q_OBJECT; 5 | 6 | public: 7 | GetCookies(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | 10 | private: 11 | QString m_buffer; 12 | }; 13 | -------------------------------------------------------------------------------- /src/GetTimeout.cpp: -------------------------------------------------------------------------------- 1 | #include "GetTimeout.h" 2 | #include "WebPageManager.h" 3 | 4 | GetTimeout::GetTimeout(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 5 | } 6 | 7 | void GetTimeout::start() { 8 | finish(true, QString::number(manager()->getTimeout())); 9 | } 10 | -------------------------------------------------------------------------------- /src/GetTimeout.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class WebPageManager; 4 | 5 | class GetTimeout : public SocketCommand { 6 | Q_OBJECT; 7 | 8 | public: 9 | GetTimeout(WebPageManager *page, QStringList &arguments, QObject *parent = 0); 10 | virtual void start(); 11 | }; 12 | -------------------------------------------------------------------------------- /src/GetWindowHandle.cpp: -------------------------------------------------------------------------------- 1 | #include "GetWindowHandle.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include 5 | 6 | GetWindowHandle::GetWindowHandle(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void GetWindowHandle::start() { 10 | finish(true, page()->uuid()); 11 | } 12 | -------------------------------------------------------------------------------- /src/GetWindowHandle.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class GetWindowHandle : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | GetWindowHandle(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/GetWindowHandles.cpp: -------------------------------------------------------------------------------- 1 | #include "GetWindowHandles.h" 2 | #include "WebPageManager.h" 3 | #include "CommandFactory.h" 4 | #include "WebPage.h" 5 | #include "JsonSerializer.h" 6 | #include 7 | 8 | GetWindowHandles::GetWindowHandles(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 9 | } 10 | 11 | void GetWindowHandles::start() { 12 | QVariantList handles; 13 | 14 | foreach(WebPage *page, manager()->pages()) 15 | handles << page->uuid(); 16 | 17 | JsonSerializer serializer; 18 | QByteArray json = serializer.serialize(handles); 19 | 20 | finish(true, json); 21 | } 22 | -------------------------------------------------------------------------------- /src/GetWindowHandles.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class GetWindowHandles : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | GetWindowHandles(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/Header.cpp: -------------------------------------------------------------------------------- 1 | #include "Header.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "NetworkAccessManager.h" 5 | 6 | Header::Header(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void Header::start() { 10 | QString key = arguments()[0]; 11 | QString value = arguments()[1]; 12 | NetworkAccessManager* networkAccessManager = manager()->networkAccessManager(); 13 | if (key.toLower().replace("-", "_") == "user_agent") { 14 | page()->setUserAgent(value); 15 | } else { 16 | networkAccessManager->addHeader(key, value); 17 | } 18 | finish(true); 19 | } 20 | -------------------------------------------------------------------------------- /src/Header.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class Header : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | Header(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/Headers.cpp: -------------------------------------------------------------------------------- 1 | #include "Headers.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | 5 | Headers::Headers(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 6 | } 7 | 8 | void Headers::start() { 9 | finish(true, page()->pageHeaders().join("\r")); 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/Headers.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class Headers : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | Headers(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/IgnoreDebugOutput.cpp: -------------------------------------------------------------------------------- 1 | #include "IgnoreDebugOutput.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | void debugIgnoringMessageHandler(QtMsgType type, const char *msg); 8 | 9 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 10 | void debugIgnoringMessageHandlerQt5(QtMsgType type, const QMessageLogContext &context, const QString &message); 11 | #endif 12 | 13 | void debugIgnoringMessageHandler(QtMsgType type, const char *msg) { 14 | switch (type) { 15 | case QtDebugMsg: 16 | case QtWarningMsg: 17 | break; 18 | default: 19 | fprintf(stderr, "%s\n", msg); 20 | break; 21 | } 22 | } 23 | 24 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 25 | void debugIgnoringMessageHandlerQt5(QtMsgType type, const QMessageLogContext &context, const QString &message) { 26 | Q_UNUSED(context); 27 | debugIgnoringMessageHandler(type, message.toLocal8Bit().data()); 28 | } 29 | #endif 30 | 31 | void ignoreDebugOutput(void) { 32 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 33 | qInstallMessageHandler(debugIgnoringMessageHandlerQt5); 34 | #else 35 | qInstallMsgHandler(debugIgnoringMessageHandler); 36 | #endif 37 | } 38 | -------------------------------------------------------------------------------- /src/IgnoreDebugOutput.h: -------------------------------------------------------------------------------- 1 | void ignoreDebugOutput(void); 2 | -------------------------------------------------------------------------------- /src/IgnoreSslErrors.cpp: -------------------------------------------------------------------------------- 1 | #include "IgnoreSslErrors.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | 5 | IgnoreSslErrors::IgnoreSslErrors(WebPageManager *manager, QStringList &arguments, QObject *parent) : 6 | SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void IgnoreSslErrors::start() { 10 | manager()->setIgnoreSslErrors(true); 11 | finish(true); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/IgnoreSslErrors.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class IgnoreSslErrors : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | IgnoreSslErrors(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/InvocationResult.cpp: -------------------------------------------------------------------------------- 1 | #include "InvocationResult.h" 2 | #include "ErrorMessage.h" 3 | 4 | InvocationResult::InvocationResult(QVariant result, bool error) { 5 | m_result = result; 6 | m_error = error; 7 | } 8 | 9 | const QVariant &InvocationResult::result() const { 10 | return m_result; 11 | } 12 | 13 | bool InvocationResult::hasError() { 14 | return m_error; 15 | } 16 | 17 | ErrorMessage *InvocationResult::errorMessage() { 18 | if (!m_result.canConvert()) 19 | return new ErrorMessage(m_result.toString()); 20 | 21 | QVariantMap error = m_result.toMap(); 22 | 23 | QString message = error["message"].toString(); 24 | 25 | if (error["name"] == "Capybara.ClickFailed") 26 | return new ErrorMessage("ClickFailed", message); 27 | else if (error["name"] == "Capybara.NodeNotAttachedError") 28 | return new ErrorMessage("NodeNotAttachedError", message); 29 | else 30 | return new ErrorMessage(message); 31 | } 32 | -------------------------------------------------------------------------------- /src/InvocationResult.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class ErrorMessage; 4 | 5 | class InvocationResult { 6 | public: 7 | InvocationResult(QVariant result, bool error = false); 8 | const QVariant &result() const; 9 | bool hasError(); 10 | ErrorMessage *errorMessage(); 11 | 12 | private: 13 | QVariant m_result; 14 | bool m_error; 15 | }; 16 | 17 | -------------------------------------------------------------------------------- /src/JavascriptAlertMessages.cpp: -------------------------------------------------------------------------------- 1 | #include "JavascriptAlertMessages.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "JsonSerializer.h" 5 | 6 | JavascriptAlertMessages::JavascriptAlertMessages(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} 7 | 8 | void JavascriptAlertMessages::start() 9 | { 10 | JsonSerializer serializer; 11 | QByteArray json = serializer.serialize(page()->alertMessages()); 12 | finish(true, json); 13 | } 14 | -------------------------------------------------------------------------------- /src/JavascriptAlertMessages.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class JavascriptAlertMessages : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | JavascriptAlertMessages(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/JavascriptCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "JavascriptCommand.h" 2 | #include "WebPageManager.h" 3 | #include "InvocationResult.h" 4 | 5 | JavascriptCommand::JavascriptCommand(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 6 | } 7 | 8 | void JavascriptCommand::finish(InvocationResult *result) { 9 | if (result->hasError()) 10 | SocketCommand::finish(false, result->errorMessage()); 11 | else { 12 | QString message = result->result().toString(); 13 | SocketCommand::finish(true, message); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/JavascriptCommand.h: -------------------------------------------------------------------------------- 1 | #ifndef JAVASCRIPT_COMMAND_H 2 | #define JAVASCRIPT_COMMAND_H 3 | 4 | #include 5 | #include 6 | #include "SocketCommand.h" 7 | 8 | class WebPage; 9 | class WebPageManager; 10 | class InvocationResult; 11 | 12 | class JavascriptCommand : public SocketCommand { 13 | Q_OBJECT 14 | 15 | public: 16 | JavascriptCommand(WebPageManager *, QStringList &arguments, QObject *parent = 0); 17 | void finish(InvocationResult *result); 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/JavascriptConfirmMessages.cpp: -------------------------------------------------------------------------------- 1 | #include "JavascriptConfirmMessages.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "JsonSerializer.h" 5 | 6 | JavascriptConfirmMessages::JavascriptConfirmMessages(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} 7 | 8 | void JavascriptConfirmMessages::start() 9 | { 10 | JsonSerializer serializer; 11 | QByteArray json = serializer.serialize(page()->confirmMessages()); 12 | finish(true, json); 13 | } 14 | -------------------------------------------------------------------------------- /src/JavascriptConfirmMessages.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class JavascriptConfirmMessages : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | JavascriptConfirmMessages(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/JavascriptInvocation.cpp: -------------------------------------------------------------------------------- 1 | #include "JavascriptInvocation.h" 2 | #include "WebPage.h" 3 | #include "InvocationResult.h" 4 | #include 5 | #include 6 | #include 7 | 8 | JavascriptInvocation::JavascriptInvocation(const QString &functionName, bool allowUnattached, const QStringList &arguments, WebPage *page, QObject *parent) : QObject(parent) { 9 | m_functionName = functionName; 10 | m_allowUnattached = allowUnattached; 11 | m_arguments = arguments; 12 | m_page = page; 13 | } 14 | 15 | QString &JavascriptInvocation::functionName() { 16 | return m_functionName; 17 | } 18 | 19 | bool JavascriptInvocation::allowUnattached() { 20 | return m_allowUnattached; 21 | } 22 | 23 | QStringList &JavascriptInvocation::arguments() { 24 | return m_arguments; 25 | } 26 | 27 | QVariant JavascriptInvocation::getError() { 28 | return m_error; 29 | } 30 | 31 | void JavascriptInvocation::setError(QVariant error) { 32 | m_error = error; 33 | } 34 | 35 | InvocationResult JavascriptInvocation::invoke(QWebFrame *frame) { 36 | frame->addToJavaScriptWindowObject("CapybaraInvocation", this); 37 | QVariant result = frame->evaluateJavaScript("Capybara.invoke()"); 38 | if (getError().isValid()) 39 | return InvocationResult(getError(), true); 40 | else 41 | return InvocationResult(result); 42 | } 43 | 44 | void JavascriptInvocation::leftClick(int x, int y) { 45 | QPoint mousePos(x, y); 46 | 47 | m_page->mouseEvent(QEvent::MouseButtonPress, mousePos, Qt::LeftButton); 48 | m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::LeftButton); 49 | } 50 | 51 | void JavascriptInvocation::rightClick(int x, int y) { 52 | QPoint mousePos(x, y); 53 | 54 | m_page->mouseEvent(QEvent::MouseButtonPress, mousePos, Qt::RightButton); 55 | 56 | // swallowContextMenuEvent tries to fire contextmenu event in html page 57 | QContextMenuEvent *event = new QContextMenuEvent(QContextMenuEvent::Mouse, mousePos); 58 | m_page->swallowContextMenuEvent(event); 59 | 60 | m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::RightButton); 61 | } 62 | 63 | void JavascriptInvocation::doubleClick(int x, int y) { 64 | QPoint mousePos(x, y); 65 | 66 | m_page->mouseEvent(QEvent::MouseButtonDblClick, mousePos, Qt::LeftButton); 67 | m_page->mouseEvent(QEvent::MouseButtonRelease, mousePos, Qt::LeftButton); 68 | } 69 | 70 | bool JavascriptInvocation::clickTest(QWebElement element, int absoluteX, int absoluteY) { 71 | return m_page->clickTest(element, absoluteX, absoluteY); 72 | } 73 | 74 | QVariantMap JavascriptInvocation::clickPosition(QWebElement element, int left, int top, int width, int height) { 75 | QRect elementBox(left, top, width, height); 76 | QRect viewport(QPoint(0, 0), m_page->viewportSize()); 77 | QRect boundedBox = elementBox.intersected(viewport); 78 | QPoint mousePos = boundedBox.center(); 79 | 80 | QVariantMap m; 81 | m["relativeX"] = mousePos.x(); 82 | m["relativeY"] = mousePos.y(); 83 | 84 | QWebFrame *parent = element.webFrame(); 85 | while (parent) { 86 | elementBox.translate(parent->geometry().topLeft()); 87 | parent = parent->parentFrame(); 88 | } 89 | 90 | boundedBox = elementBox.intersected(viewport); 91 | mousePos = boundedBox.center(); 92 | 93 | m["absoluteX"] = mousePos.x(); 94 | m["absoluteY"] = mousePos.y(); 95 | 96 | return m; 97 | } 98 | 99 | void JavascriptInvocation::hover(int absoluteX, int absoluteY) { 100 | QPoint mousePos(absoluteX, absoluteY); 101 | hover(mousePos); 102 | } 103 | 104 | void JavascriptInvocation::hover(const QPoint &mousePos) { 105 | m_page->mouseEvent(QEvent::MouseMove, mousePos, Qt::NoButton); 106 | } 107 | 108 | int JavascriptInvocation::keyCodeFor(const QChar &key) { 109 | switch(key.unicode()) { 110 | case 0x18: 111 | return Qt::Key_Cancel; 112 | case 0x08: 113 | return Qt::Key_Backspace; 114 | case 0x09: 115 | return Qt::Key_Tab; 116 | case 0x0A: 117 | return Qt::Key_Return; 118 | case 0x1B: 119 | return Qt::Key_Escape; 120 | case 0x7F: 121 | return Qt::Key_Delete; 122 | default: 123 | int keyCode = key.toUpper().toLatin1(); 124 | if (keyCode >= Qt::Key_Space || keyCode <= Qt::Key_AsciiTilde) 125 | return keyCode; 126 | else 127 | return Qt::Key_unknown; 128 | } 129 | } 130 | 131 | void JavascriptInvocation::keypress(QChar key) { 132 | int keyCode = keyCodeFor(key); 133 | QKeyEvent event(QKeyEvent::KeyPress, keyCode, Qt::NoModifier, key); 134 | QApplication::sendEvent(m_page, &event); 135 | event = QKeyEvent(QKeyEvent::KeyRelease, keyCode, Qt::NoModifier, key); 136 | QApplication::sendEvent(m_page, &event); 137 | } 138 | 139 | const QString JavascriptInvocation::render(void) { 140 | QString pathTemplate = 141 | QDir::temp().absoluteFilePath("./click_failed_XXXXXX.png"); 142 | QTemporaryFile file(pathTemplate); 143 | file.open(); 144 | file.setAutoRemove(false); 145 | QString path = file.fileName(); 146 | m_page->render(path, QSize(1024, 768)); 147 | return path; 148 | } 149 | -------------------------------------------------------------------------------- /src/JavascriptInvocation.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | class WebPage; 8 | class InvocationResult; 9 | 10 | class JavascriptInvocation : public QObject { 11 | Q_OBJECT 12 | Q_PROPERTY(QString functionName READ functionName) 13 | Q_PROPERTY(bool allowUnattached READ allowUnattached) 14 | Q_PROPERTY(QStringList arguments READ arguments) 15 | Q_PROPERTY(QVariant error READ getError WRITE setError) 16 | 17 | public: 18 | JavascriptInvocation(const QString &functionName, bool allowUnattached, const QStringList &arguments, WebPage *page, QObject *parent = 0); 19 | QString &functionName(); 20 | bool allowUnattached(); 21 | QStringList &arguments(); 22 | Q_INVOKABLE void leftClick(int x, int y); 23 | Q_INVOKABLE void rightClick(int x, int y); 24 | Q_INVOKABLE void doubleClick(int x, int y); 25 | Q_INVOKABLE bool clickTest(QWebElement element, int absoluteX, int absoluteY); 26 | Q_INVOKABLE QVariantMap clickPosition(QWebElement element, int left, int top, int width, int height); 27 | Q_INVOKABLE void hover(int absoluteX, int absoluteY); 28 | Q_INVOKABLE void keypress(QChar); 29 | Q_INVOKABLE const QString render(void); 30 | QVariant getError(); 31 | void setError(QVariant error); 32 | InvocationResult invoke(QWebFrame *); 33 | 34 | private: 35 | QString m_functionName; 36 | bool m_allowUnattached; 37 | QStringList m_arguments; 38 | WebPage *m_page; 39 | QVariant m_error; 40 | void hover(const QPoint &); 41 | int keyCodeFor(const QChar &); 42 | }; 43 | 44 | -------------------------------------------------------------------------------- /src/JavascriptPromptMessages.cpp: -------------------------------------------------------------------------------- 1 | #include "JavascriptPromptMessages.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "JsonSerializer.h" 5 | 6 | JavascriptPromptMessages::JavascriptPromptMessages(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} 7 | 8 | void JavascriptPromptMessages::start() 9 | { 10 | JsonSerializer serializer; 11 | QByteArray json = serializer.serialize(page()->promptMessages()); 12 | finish(true, json); 13 | } 14 | -------------------------------------------------------------------------------- /src/JavascriptPromptMessages.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class JavascriptPromptMessages : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | JavascriptPromptMessages(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/JsonSerializer.cpp: -------------------------------------------------------------------------------- 1 | #include "JsonSerializer.h" 2 | #include 3 | 4 | JsonSerializer::JsonSerializer(QObject *parent) : QObject(parent) { 5 | } 6 | 7 | QByteArray JsonSerializer::serialize(const QVariant &object) { 8 | addVariant(object); 9 | return m_buffer; 10 | } 11 | 12 | void JsonSerializer::addVariant(const QVariant &object) { 13 | if (object.isValid()) { 14 | switch(object.type()) { 15 | case QMetaType::QString: 16 | { 17 | QString string = object.toString(); 18 | addString(string); 19 | } 20 | break; 21 | case QMetaType::QVariantList: 22 | { 23 | QVariantList list = object.toList(); 24 | addArray(list); 25 | } 26 | break; 27 | case QMetaType::Double: 28 | if (std::isinf(object.toDouble())) 29 | m_buffer.append("null"); 30 | else 31 | m_buffer.append(object.toString()); 32 | break; 33 | case QMetaType::QVariantMap: 34 | { 35 | QVariantMap map = object.toMap(); 36 | addMap(map); 37 | break; 38 | } 39 | case QMetaType::Bool: 40 | { 41 | m_buffer.append(object.toString()); 42 | break; 43 | } 44 | case QMetaType::Int: 45 | { 46 | m_buffer.append(object.toString()); 47 | break; 48 | } 49 | default: 50 | m_buffer.append("null"); 51 | } 52 | } else { 53 | m_buffer.append("null"); 54 | } 55 | } 56 | 57 | void JsonSerializer::addString(const QString &string) { 58 | m_buffer.append("\""); 59 | m_buffer.append(sanitizeString(string)); 60 | m_buffer.append("\""); 61 | } 62 | 63 | void JsonSerializer::addArray(const QVariantList &list) { 64 | m_buffer.append("["); 65 | for (int i = 0; i < list.length(); i++) { 66 | if (i > 0) 67 | m_buffer.append(","); 68 | addVariant(list[i]); 69 | } 70 | m_buffer.append("]"); 71 | } 72 | 73 | void JsonSerializer::addMap(const QVariantMap &map) { 74 | m_buffer.append("{"); 75 | QMapIterator iterator(map); 76 | while (iterator.hasNext()) { 77 | iterator.next(); 78 | QString key = iterator.key(); 79 | QVariant value = iterator.value(); 80 | addString(key); 81 | m_buffer.append(":"); 82 | addVariant(value); 83 | if (iterator.hasNext()) 84 | m_buffer.append(","); 85 | } 86 | m_buffer.append("}"); 87 | } 88 | 89 | QByteArray JsonSerializer::sanitizeString(QString str) { 90 | str.replace("\\", "\\\\"); 91 | str.replace("\"", "\\\""); 92 | str.replace("\b", "\\b"); 93 | str.replace("\f", "\\f"); 94 | str.replace("\n", "\\n"); 95 | str.replace("\r", "\\r"); 96 | str.replace("\t", "\\t"); 97 | 98 | QByteArray result; 99 | const ushort* unicode = str.utf16(); 100 | unsigned int i = 0; 101 | 102 | while (unicode[i]) { 103 | if (unicode[i] > 31 && unicode[i] < 128) { 104 | result.append(unicode[i]); 105 | } 106 | else { 107 | QString hexCode = QString::number(unicode[i], 16).rightJustified(4, '0'); 108 | 109 | result.append("\\u").append(hexCode); 110 | } 111 | ++i; 112 | } 113 | 114 | return result; 115 | } 116 | 117 | -------------------------------------------------------------------------------- /src/JsonSerializer.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class JsonSerializer : public QObject { 5 | Q_OBJECT 6 | 7 | public: 8 | JsonSerializer(QObject *parent = 0); 9 | QByteArray serialize(const QVariant &object); 10 | 11 | private: 12 | void addVariant(const QVariant &object); 13 | void addString(const QString &string); 14 | void addArray(const QVariantList &list); 15 | void addMap(const QVariantMap &map); 16 | QByteArray sanitizeString(QString string); 17 | 18 | QByteArray m_buffer; 19 | }; 20 | 21 | -------------------------------------------------------------------------------- /src/NetworkAccessManager.cpp: -------------------------------------------------------------------------------- 1 | #include "NetworkAccessManager.h" 2 | #include "WebPage.h" 3 | #include 4 | #include 5 | #include "NoOpReply.h" 6 | #include "NetworkReplyProxy.h" 7 | 8 | NetworkAccessManager::NetworkAccessManager(QObject *parent):QNetworkAccessManager(parent) { 9 | connect(this, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), SLOT(provideAuthentication(QNetworkReply*,QAuthenticator*))); 10 | connect(this, SIGNAL(finished(QNetworkReply *)), this, SLOT(finished(QNetworkReply *))); 11 | disableKeyChainLookup(); 12 | } 13 | 14 | QNetworkReply* NetworkAccessManager::createRequest(QNetworkAccessManager::Operation operation, const QNetworkRequest &request, QIODevice * outgoingData = 0) { 15 | QNetworkRequest new_request(request); 16 | QByteArray url = new_request.url().toEncoded(); 17 | if (this->isBlacklisted(new_request.url())) { 18 | return new NetworkReplyProxy(new NoOpReply(new_request), this); 19 | } else { 20 | if (operation != QNetworkAccessManager::PostOperation && operation != QNetworkAccessManager::PutOperation) { 21 | new_request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant()); 22 | } 23 | QHashIterator item(m_headers); 24 | while (item.hasNext()) { 25 | item.next(); 26 | new_request.setRawHeader(item.key().toLatin1(), item.value().toLatin1()); 27 | } 28 | QNetworkReply *reply = new NetworkReplyProxy(QNetworkAccessManager::createRequest(operation, new_request, outgoingData), this); 29 | emit requestCreated(url, reply); 30 | return reply; 31 | } 32 | }; 33 | 34 | void NetworkAccessManager::finished(QNetworkReply *reply) { 35 | QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); 36 | if (redirectUrl.isValid()) 37 | m_redirectMappings[reply->url().resolved(redirectUrl)] = reply->url(); 38 | else { 39 | QUrl requestedUrl = reply->url(); 40 | while (m_redirectMappings.contains(requestedUrl)) 41 | requestedUrl = m_redirectMappings.take(requestedUrl); 42 | emit finished(requestedUrl, reply); 43 | } 44 | } 45 | 46 | void NetworkAccessManager::addHeader(QString key, QString value) { 47 | m_headers.insert(key, value); 48 | } 49 | 50 | void NetworkAccessManager::reset() { 51 | m_headers.clear(); 52 | m_userName = QString(); 53 | m_password = QString(); 54 | } 55 | 56 | void NetworkAccessManager::setUserName(const QString &userName) { 57 | m_userName = userName; 58 | } 59 | 60 | void NetworkAccessManager::setPassword(const QString &password) { 61 | m_password = password; 62 | } 63 | 64 | void NetworkAccessManager::provideAuthentication(QNetworkReply *reply, QAuthenticator *authenticator) { 65 | Q_UNUSED(reply); 66 | if (m_userName != authenticator->user()) 67 | authenticator->setUser(m_userName); 68 | if (m_password != authenticator->password()) 69 | authenticator->setPassword(m_password); 70 | } 71 | 72 | void NetworkAccessManager::setUrlBlacklist(QStringList urlBlacklist) { 73 | m_urlBlacklist.clear(); 74 | 75 | QStringListIterator iter(urlBlacklist); 76 | while (iter.hasNext()) { 77 | m_urlBlacklist << QUrl(iter.next()); 78 | } 79 | }; 80 | 81 | bool NetworkAccessManager::isBlacklisted(QUrl url) { 82 | QListIterator iter(m_urlBlacklist); 83 | 84 | while (iter.hasNext()) { 85 | QUrl blacklisted = iter.next(); 86 | 87 | if (blacklisted == url) { 88 | return true; 89 | } else if (blacklisted.path().isEmpty() && blacklisted.isParentOf(url)) { 90 | return true; 91 | } 92 | } 93 | 94 | return false; 95 | }; 96 | 97 | /* 98 | * This is a workaround for a Qt 5/OS X bug: 99 | * https://bugreports.qt-project.org/browse/QTBUG-30434 100 | */ 101 | void NetworkAccessManager::disableKeyChainLookup() { 102 | QNetworkProxy fixedProxy = proxy(); 103 | fixedProxy.setHostName(" "); 104 | setProxy(fixedProxy); 105 | } 106 | -------------------------------------------------------------------------------- /src/NetworkAccessManager.h: -------------------------------------------------------------------------------- 1 | #ifndef __NETWORKACCESSMANAGER_H 2 | #define __NETWORKACCESSMANAGER_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class NetworkAccessManager : public QNetworkAccessManager { 9 | 10 | Q_OBJECT 11 | 12 | public: 13 | NetworkAccessManager(QObject *parent = 0); 14 | void addHeader(QString key, QString value); 15 | void reset(); 16 | void setUserName(const QString &userName); 17 | void setPassword(const QString &password); 18 | void setUrlBlacklist(QStringList urlBlacklist); 19 | 20 | protected: 21 | QNetworkReply* createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice * outgoingData); 22 | QString m_userName; 23 | QString m_password; 24 | QList m_urlBlacklist; 25 | 26 | private: 27 | void disableKeyChainLookup(); 28 | 29 | QHash m_headers; 30 | bool isBlacklisted(QUrl url); 31 | QHash m_redirectMappings; 32 | 33 | private slots: 34 | void provideAuthentication(QNetworkReply *reply, QAuthenticator *authenticator); 35 | void finished(QNetworkReply *); 36 | 37 | signals: 38 | void requestCreated(QByteArray &url, QNetworkReply *reply); 39 | void finished(QUrl &, QNetworkReply *); 40 | }; 41 | #endif 42 | -------------------------------------------------------------------------------- /src/NetworkCookieJar.cpp: -------------------------------------------------------------------------------- 1 | #include "NetworkCookieJar.h" 2 | #include "QtCore/qdatetime.h" 3 | 4 | NetworkCookieJar::NetworkCookieJar(QObject *parent) 5 | : QNetworkCookieJar(parent) 6 | { } 7 | 8 | QList NetworkCookieJar::getAllCookies() const 9 | { 10 | return allCookies(); 11 | } 12 | 13 | void NetworkCookieJar::clearCookies() 14 | { 15 | setAllCookies(QList()); 16 | } 17 | 18 | static inline bool isParentDomain(QString domain, QString reference) 19 | { 20 | if (!reference.startsWith(QLatin1Char('.'))) 21 | return domain == reference; 22 | 23 | return domain.endsWith(reference) || domain == reference.mid(1); 24 | } 25 | 26 | void NetworkCookieJar::overwriteCookies(const QList& cookieList) 27 | { 28 | /* this function is basically a copy-and-paste of the original 29 | QNetworkCookieJar::setCookiesFromUrl with the domain and 30 | path validations removed */ 31 | 32 | QString defaultPath(QLatin1Char('/')); 33 | QDateTime now = QDateTime::currentDateTime(); 34 | QList newCookies = allCookies(); 35 | 36 | foreach (QNetworkCookie cookie, cookieList) { 37 | bool isDeletion = (!cookie.isSessionCookie() && 38 | cookie.expirationDate() < now); 39 | 40 | // validate the cookie & set the defaults if unset 41 | if (cookie.path().isEmpty()) 42 | cookie.setPath(defaultPath); 43 | 44 | // don't do path checking. See http://bugreports.qt.nokia.com/browse/QTBUG-5815 45 | // else if (!isParentPath(pathAndFileName, cookie.path())) { 46 | // continue; // not accepted 47 | // } 48 | 49 | if (cookie.domain().isEmpty()) { 50 | continue; 51 | } else { 52 | // Ensure the domain starts with a dot if its field was not empty 53 | // in the HTTP header. There are some servers that forget the 54 | // leading dot and this is actually forbidden according to RFC 2109, 55 | // but all browsers accept it anyway so we do that as well. 56 | if (!cookie.domain().startsWith(QLatin1Char('.'))) 57 | cookie.setDomain(QLatin1Char('.') + cookie.domain()); 58 | 59 | QString domain = cookie.domain(); 60 | 61 | // the check for effective TLDs makes the "embedded dot" rule from RFC 2109 section 4.3.2 62 | // redundant; the "leading dot" rule has been relaxed anyway, see above 63 | // we remove the leading dot for this check 64 | /* 65 | if (QNetworkCookieJarPrivate::isEffectiveTLD(domain.remove(0, 1))) 66 | continue; // not accepted 67 | */ 68 | } 69 | 70 | for (int i = 0; i < newCookies.size(); ++i) { 71 | // does this cookie already exist? 72 | const QNetworkCookie ¤t = newCookies.at(i); 73 | if (cookie.name() == current.name() && 74 | cookie.domain() == current.domain() && 75 | cookie.path() == current.path()) { 76 | // found a match 77 | newCookies.removeAt(i); 78 | break; 79 | } 80 | } 81 | 82 | // did not find a match 83 | if (!isDeletion) { 84 | int countForDomain = 0; 85 | for (int i = newCookies.size() - 1; i >= 0; --i) { 86 | // Start from the end and delete the oldest cookies to keep a maximum count of 50. 87 | const QNetworkCookie ¤t = newCookies.at(i); 88 | if (isParentDomain(cookie.domain(), current.domain()) 89 | || isParentDomain(current.domain(), cookie.domain())) { 90 | if (countForDomain >= 49) 91 | newCookies.removeAt(i); 92 | else 93 | ++countForDomain; 94 | } 95 | } 96 | 97 | newCookies += cookie; 98 | } 99 | } 100 | setAllCookies(newCookies); 101 | } 102 | -------------------------------------------------------------------------------- /src/NetworkCookieJar.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class NetworkCookieJar : public QNetworkCookieJar { 5 | 6 | Q_OBJECT; 7 | 8 | public: 9 | 10 | NetworkCookieJar(QObject *parent = 0); 11 | 12 | QList getAllCookies() const; 13 | void clearCookies(); 14 | void overwriteCookies(const QList& cookieList); 15 | }; 16 | -------------------------------------------------------------------------------- /src/NetworkReplyProxy.cpp: -------------------------------------------------------------------------------- 1 | #include "NetworkReplyProxy.h" 2 | 3 | NetworkReplyProxy::NetworkReplyProxy(QNetworkReply* reply, QObject* parent) 4 | : QNetworkReply(parent) 5 | , m_reply(reply) 6 | { 7 | m_reply->setParent(this); 8 | 9 | setOperation(m_reply->operation()); 10 | setRequest(m_reply->request()); 11 | setUrl(m_reply->url()); 12 | 13 | if (m_reply->isFinished()) { 14 | readInternal(); 15 | setFinished(true); 16 | } 17 | 18 | applyMetaData(); 19 | 20 | connect(m_reply, SIGNAL(metaDataChanged()), SLOT(applyMetaData())); 21 | connect(m_reply, SIGNAL(readyRead()), SLOT(handleReadyRead())); 22 | connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(errorInternal(QNetworkReply::NetworkError))); 23 | connect(m_reply, SIGNAL(finished()), SLOT(handleFinished())); 24 | 25 | connect(m_reply, SIGNAL(uploadProgress(qint64,qint64)), SIGNAL(uploadProgress(qint64,qint64))); 26 | connect(m_reply, SIGNAL(downloadProgress(qint64,qint64)), SIGNAL(downloadProgress(qint64,qint64))); 27 | connect(m_reply, SIGNAL(sslErrors(const QList &)), SIGNAL(sslErrors(const QList &))); 28 | 29 | setOpenMode(ReadOnly); 30 | } 31 | 32 | void NetworkReplyProxy::abort() { m_reply->abort(); } 33 | void NetworkReplyProxy::close() { m_reply->close(); } 34 | bool NetworkReplyProxy::isSequential() const { return m_reply->isSequential(); } 35 | 36 | void NetworkReplyProxy::handleFinished() { 37 | setFinished(true); 38 | emit finished(); 39 | } 40 | 41 | qint64 NetworkReplyProxy::bytesAvailable() const 42 | { 43 | return m_buffer.size() + QIODevice::bytesAvailable(); 44 | } 45 | 46 | qint64 NetworkReplyProxy::readData(char* data, qint64 maxlen) 47 | { 48 | qint64 size = qMin(maxlen, qint64(m_buffer.size())); 49 | memcpy(data, m_buffer.constData(), size); 50 | m_buffer.remove(0, size); 51 | return size; 52 | } 53 | 54 | void NetworkReplyProxy::ignoreSslErrors() { m_reply->ignoreSslErrors(); } 55 | void NetworkReplyProxy::applyMetaData() { 56 | foreach(QNetworkReply::RawHeaderPair header, m_reply->rawHeaderPairs()) 57 | setRawHeader(header.first, header.second); 58 | 59 | setAttribute(QNetworkRequest::HttpStatusCodeAttribute, m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute)); 60 | setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute)); 61 | setAttribute(QNetworkRequest::RedirectionTargetAttribute, m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute)); 62 | setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, m_reply->attribute(QNetworkRequest::ConnectionEncryptedAttribute)); 63 | setAttribute(QNetworkRequest::CacheLoadControlAttribute, m_reply->attribute(QNetworkRequest::CacheLoadControlAttribute)); 64 | setAttribute(QNetworkRequest::CacheSaveControlAttribute, m_reply->attribute(QNetworkRequest::CacheSaveControlAttribute)); 65 | setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, m_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute)); 66 | setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, m_reply->attribute(QNetworkRequest::DoNotBufferUploadDataAttribute)); 67 | emit metaDataChanged(); 68 | } 69 | 70 | void NetworkReplyProxy::errorInternal(QNetworkReply::NetworkError _error) 71 | { 72 | setError(_error, errorString()); 73 | emit error(_error); 74 | } 75 | 76 | void NetworkReplyProxy::readInternal() { 77 | QByteArray data = m_reply->readAll(); 78 | m_data += data; 79 | m_buffer += data; 80 | } 81 | 82 | void NetworkReplyProxy::handleReadyRead() 83 | { 84 | readInternal(); 85 | emit readyRead(); 86 | } 87 | 88 | QByteArray NetworkReplyProxy::data() { 89 | return m_data; 90 | } 91 | 92 | -------------------------------------------------------------------------------- /src/NetworkReplyProxy.h: -------------------------------------------------------------------------------- 1 | #ifndef _NETWORKREPLYPROXY_H 2 | #define _NETWORKREPLYPROXY_H 3 | /* 4 | * Copyright (C) 2009, 2010 Nokia Corporation and/or its subsidiary(-ies) 5 | * Copyright (C) 2009, 2010 Holger Hans Peter Freyther 6 | * 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions 11 | * are met: 12 | * 1. Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in the 16 | * documentation and/or other materials provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 22 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 23 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 26 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #include 32 | #include 33 | 34 | class NetworkReplyProxy : public QNetworkReply { 35 | Q_OBJECT 36 | 37 | public: 38 | NetworkReplyProxy(QNetworkReply* reply, QObject* parent); 39 | 40 | virtual void abort(); 41 | virtual void close(); 42 | virtual bool isSequential() const; 43 | 44 | virtual qint64 bytesAvailable() const; 45 | 46 | virtual qint64 readData(char* data, qint64 maxlen); 47 | 48 | QByteArray data(); 49 | 50 | public slots: 51 | void ignoreSslErrors(); 52 | void applyMetaData(); 53 | void errorInternal(QNetworkReply::NetworkError _error); 54 | void handleReadyRead(); 55 | void handleFinished(); 56 | 57 | private: 58 | void readInternal(); 59 | 60 | QNetworkReply* m_reply; 61 | QByteArray m_data; 62 | QByteArray m_buffer; 63 | }; 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /src/NoOpReply.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "NoOpReply.h" 3 | 4 | NoOpReply::NoOpReply(QNetworkRequest &request, QObject *parent) : QNetworkReply(parent) { 5 | open(ReadOnly | Unbuffered); 6 | setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200); 7 | setHeader(QNetworkRequest::ContentLengthHeader, QVariant(0)); 8 | setHeader(QNetworkRequest::ContentTypeHeader, QVariant(QString("text/plain"))); 9 | setUrl(request.url()); 10 | 11 | QTimer::singleShot( 0, this, SIGNAL(readyRead()) ); 12 | QTimer::singleShot( 0, this, SIGNAL(finished()) ); 13 | } 14 | 15 | void NoOpReply::abort() { 16 | // NO-OP 17 | } 18 | 19 | qint64 NoOpReply::bytesAvailable() const { 20 | return 0; 21 | } 22 | 23 | bool NoOpReply::isSequential() const { 24 | return true; 25 | } 26 | 27 | qint64 NoOpReply::readData(char *data, qint64 maxSize) { 28 | Q_UNUSED(data); 29 | Q_UNUSED(maxSize); 30 | return 0; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/NoOpReply.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class NoOpReply : public QNetworkReply { 5 | Q_OBJECT 6 | 7 | public: 8 | NoOpReply(QNetworkRequest &request, QObject *parent = 0); 9 | 10 | void abort(); 11 | qint64 bytesAvailable() const; 12 | bool isSequential() const; 13 | 14 | protected: 15 | qint64 readData(char *data, qint64 maxSize); 16 | 17 | }; 18 | 19 | -------------------------------------------------------------------------------- /src/Node.cpp: -------------------------------------------------------------------------------- 1 | #include "Node.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "InvocationResult.h" 5 | 6 | Node::Node(WebPageManager *manager, QStringList &arguments, QObject *parent) : JavascriptCommand(manager, arguments, parent) { 7 | } 8 | 9 | void Node::start() { 10 | QStringList functionArguments(arguments()); 11 | QString functionName = functionArguments.takeFirst(); 12 | QString allowUnattached = functionArguments.takeFirst(); 13 | InvocationResult result = page()->invokeCapybaraFunction(functionName, allowUnattached == "true", functionArguments); 14 | finish(&result); 15 | } 16 | 17 | QString Node::toString() const { 18 | QStringList functionArguments(arguments()); 19 | return QString("Node.") + functionArguments.takeFirst(); 20 | } 21 | -------------------------------------------------------------------------------- /src/Node.h: -------------------------------------------------------------------------------- 1 | #include "JavascriptCommand.h" 2 | #include 3 | 4 | class Node : public JavascriptCommand { 5 | Q_OBJECT 6 | 7 | public: 8 | Node(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); 9 | virtual void start(); 10 | virtual QString toString() const; 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /src/NullCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "NullCommand.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "ErrorMessage.h" 5 | 6 | NullCommand::NullCommand(QString name, QObject *parent) : Command(parent) { 7 | m_name = name; 8 | } 9 | 10 | void NullCommand::start() { 11 | QString failure = QString("[Capybara WebKit] Unknown command: ") + m_name + "\n"; 12 | finish(false, new ErrorMessage(failure)); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/NullCommand.h: -------------------------------------------------------------------------------- 1 | #include "Command.h" 2 | 3 | class NullCommand : public Command { 4 | Q_OBJECT 5 | 6 | public: 7 | NullCommand(QString name, QObject *parent = 0); 8 | virtual void start(); 9 | 10 | private: 11 | QString m_name; 12 | }; 13 | -------------------------------------------------------------------------------- /src/PageLoadingCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "PageLoadingCommand.h" 2 | #include "SocketCommand.h" 3 | #include "WebPage.h" 4 | #include "WebPageManager.h" 5 | #include "ErrorMessage.h" 6 | 7 | PageLoadingCommand::PageLoadingCommand(Command *command, WebPageManager *manager, QObject *parent) : Command(parent) { 8 | m_manager = manager; 9 | m_command = command; 10 | m_pageLoadingFromCommand = false; 11 | m_pageSuccess = true; 12 | m_pendingResponse = NULL; 13 | m_command->setParent(this); 14 | } 15 | 16 | void PageLoadingCommand::start() { 17 | m_manager->logger() << "Started" << m_command->toString(); 18 | connect(m_command, SIGNAL(finished(Response *)), this, SLOT(commandFinished(Response *))); 19 | connect(m_manager, SIGNAL(loadStarted()), this, SLOT(pageLoadingFromCommand())); 20 | connect(m_manager, SIGNAL(pageFinished(bool)), this, SLOT(pendingLoadFinished(bool))); 21 | m_command->start(); 22 | }; 23 | 24 | void PageLoadingCommand::pendingLoadFinished(bool success) { 25 | m_pageSuccess = success; 26 | if (m_pageLoadingFromCommand) { 27 | m_pageLoadingFromCommand = false; 28 | if (m_pendingResponse) { 29 | m_manager->logger() << "Page load from command finished"; 30 | if (m_pageSuccess) { 31 | emit finished(m_pendingResponse); 32 | } else { 33 | QString message = m_manager->currentPage()->failureString(); 34 | finish(false, new ErrorMessage(message)); 35 | } 36 | } 37 | } 38 | } 39 | 40 | void PageLoadingCommand::pageLoadingFromCommand() { 41 | m_manager->logger() << m_command->toString() << "started page load"; 42 | m_pageLoadingFromCommand = true; 43 | } 44 | 45 | void PageLoadingCommand::commandFinished(Response *response) { 46 | disconnect(m_manager, SIGNAL(loadStarted()), this, SLOT(pageLoadingFromCommand())); 47 | m_manager->logger() << "Finished" << m_command->toString() << "with response" << response->toString(); 48 | 49 | if (m_pageLoadingFromCommand) 50 | m_pendingResponse = response; 51 | else 52 | emit finished(response); 53 | } 54 | -------------------------------------------------------------------------------- /src/PageLoadingCommand.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "Command.h" 4 | 5 | class Response; 6 | class WebPageManager; 7 | 8 | /* 9 | * Decorates a Command by deferring the finished() signal until any pending 10 | * page loads are complete. 11 | * 12 | * If a Command starts a page load, no signal will be emitted until the page 13 | * load is finished. 14 | * 15 | * If a pending page load fails, the command's response will be discarded and a 16 | * failure response will be emitted instead. 17 | */ 18 | class PageLoadingCommand : public Command { 19 | Q_OBJECT 20 | 21 | public: 22 | PageLoadingCommand(Command *command, WebPageManager *page, QObject *parent = 0); 23 | virtual void start(); 24 | 25 | public slots: 26 | void pageLoadingFromCommand(); 27 | void pendingLoadFinished(bool success); 28 | void commandFinished(Response *response); 29 | 30 | private: 31 | WebPageManager *m_manager; 32 | Command *m_command; 33 | Response *m_pendingResponse; 34 | bool m_pageSuccess; 35 | bool m_pageLoadingFromCommand; 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /src/Render.cpp: -------------------------------------------------------------------------------- 1 | #include "Render.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "ErrorMessage.h" 5 | 6 | Render::Render(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void Render::start() { 10 | QString imagePath = arguments()[0]; 11 | int width = arguments()[1].toInt(); 12 | int height = arguments()[2].toInt(); 13 | 14 | QSize size(width, height); 15 | 16 | bool result = page()->render( imagePath, size ); 17 | 18 | if (result) { 19 | finish(true); 20 | } else { 21 | const QString failure = QString("Unable to save %1x%2 image to %3"). 22 | arg(width). 23 | arg(height). 24 | arg(imagePath); 25 | finish(false, new ErrorMessage(failure)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Render.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | #include 3 | 4 | class Render : public SocketCommand { 5 | Q_OBJECT 6 | 7 | public: 8 | Render(WebPageManager *page, QStringList &arguments, QObject *parent = 0); 9 | virtual void start(); 10 | }; 11 | -------------------------------------------------------------------------------- /src/Reset.cpp: -------------------------------------------------------------------------------- 1 | #include "Reset.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | 5 | Reset::Reset(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 6 | } 7 | 8 | void Reset::start() { 9 | page()->triggerAction(QWebPage::Stop); 10 | 11 | manager()->reset(); 12 | 13 | finish(true); 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/Reset.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class Reset : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | Reset(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/ResizeWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "ResizeWindow.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | 5 | ResizeWindow::ResizeWindow(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 6 | } 7 | 8 | void ResizeWindow::start() { 9 | int width = arguments()[0].toInt(); 10 | int height = arguments()[1].toInt(); 11 | 12 | QSize size(width, height); 13 | page()->setViewportSize(size); 14 | 15 | finish(true); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/ResizeWindow.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class ResizeWindow : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | ResizeWindow(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/Response.cpp: -------------------------------------------------------------------------------- 1 | #include "Response.h" 2 | #include "ErrorMessage.h" 3 | #include 4 | 5 | Response::Response(bool success, QString message, QObject *parent) : QObject(parent) { 6 | m_success = success; 7 | m_message = message.toUtf8(); 8 | } 9 | 10 | Response::Response(bool success, QByteArray message, QObject *parent) : QObject(parent) { 11 | m_success = success; 12 | m_message = message; 13 | } 14 | 15 | Response::Response(bool success, ErrorMessage *message, QObject *parent) : QObject(parent) { 16 | m_success = success; 17 | m_message = message->toString(); 18 | message->deleteLater(); 19 | } 20 | 21 | Response::Response(bool success, QObject *parent) : QObject(parent) { 22 | m_success = success; 23 | } 24 | 25 | bool Response::isSuccess() const { 26 | return m_success; 27 | } 28 | 29 | QByteArray Response::message() const { 30 | return m_message; 31 | } 32 | 33 | QString Response::toString() const { 34 | return QString(m_success ? "Success(" : "Failure(") + m_message + ")"; 35 | } 36 | -------------------------------------------------------------------------------- /src/Response.h: -------------------------------------------------------------------------------- 1 | #ifndef RESPONSE_H 2 | #define RESPONSE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class ErrorMessage; 9 | 10 | class Response : public QObject { 11 | Q_OBJECT 12 | 13 | public: 14 | Response(bool success, QString message, QObject *parent = 0); 15 | Response(bool success, QByteArray message, QObject *parent = 0); 16 | Response(bool success, ErrorMessage *message, QObject *parent = 0); 17 | Response(bool success, QObject *parent); 18 | bool isSuccess() const; 19 | QByteArray message() const; 20 | QString toString() const; 21 | 22 | protected: 23 | QByteArray m_message; 24 | 25 | private: 26 | bool m_success; 27 | }; 28 | 29 | #endif 30 | 31 | -------------------------------------------------------------------------------- /src/Server.cpp: -------------------------------------------------------------------------------- 1 | #include "Server.h" 2 | #include "Connection.h" 3 | #include "WebPageManager.h" 4 | 5 | #include 6 | 7 | Server::Server(QObject *parent) : QObject(parent) { 8 | m_tcp_server = new QTcpServer(this); 9 | } 10 | 11 | bool Server::start() { 12 | connect(m_tcp_server, SIGNAL(newConnection()), this, SLOT(handleConnection())); 13 | return m_tcp_server->listen(QHostAddress::LocalHost, 0); 14 | } 15 | 16 | quint16 Server::server_port() const { 17 | return m_tcp_server->serverPort(); 18 | } 19 | 20 | void Server::handleConnection() { 21 | QTcpSocket *socket = m_tcp_server->nextPendingConnection(); 22 | new Connection(socket, new WebPageManager(this), this); 23 | } 24 | -------------------------------------------------------------------------------- /src/Server.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class QTcpServer; 4 | 5 | class Server : public QObject { 6 | Q_OBJECT 7 | 8 | public: 9 | Server(QObject *parent); 10 | bool start(); 11 | quint16 server_port() const; 12 | 13 | public slots: 14 | void handleConnection(); 15 | 16 | private: 17 | QTcpServer *m_tcp_server; 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /src/SetAttribute.cpp: -------------------------------------------------------------------------------- 1 | #include "SetAttribute.h" 2 | #include "WebPage.h" 3 | #include 4 | 5 | static QMap getAttributesByName() 6 | { 7 | QMap map; 8 | #define ADD_ATTR(attr) map.insert(#attr, QWebSettings::attr) 9 | ADD_ATTR(AutoLoadImages); 10 | ADD_ATTR(DnsPrefetchEnabled); 11 | ADD_ATTR(PluginsEnabled); 12 | ADD_ATTR(PrivateBrowsingEnabled); 13 | ADD_ATTR(JavascriptCanOpenWindows); 14 | ADD_ATTR(JavascriptCanAccessClipboard); 15 | ADD_ATTR(OfflineStorageDatabaseEnabled); 16 | ADD_ATTR(OfflineWebApplicationCacheEnabled); 17 | ADD_ATTR(LocalStorageEnabled); 18 | ADD_ATTR(LocalStorageDatabaseEnabled); 19 | ADD_ATTR(LocalContentCanAccessFileUrls); 20 | ADD_ATTR(LocalContentCanAccessRemoteUrls); 21 | ADD_ATTR(AcceleratedCompositingEnabled); 22 | ADD_ATTR(SiteSpecificQuirksEnabled); 23 | // disable setting JavascriptEnabled to false, 24 | // as our Javascript helpers won't work then 25 | //ADD_ATTR("JavascriptEnabled"); 26 | //map.insert("JavascriptEnabled", 27 | // QWebSettings::JavascriptEnabled); 28 | #undef ADD_ATTR 29 | return map; 30 | } 31 | 32 | const QMap attributes_by_name = 33 | getAttributesByName(); 34 | 35 | SetAttribute::SetAttribute(WebPageManager* manager, QStringList& args, QObject* parent) 36 | : SocketCommand(manager, args, parent) 37 | { } 38 | 39 | void SetAttribute::start() 40 | { 41 | QString name = arguments()[0], val = arguments()[1]; 42 | if (!attributes_by_name.contains(name)) { 43 | // not found 44 | finish(false, QString("No such attribute: ") + name); 45 | return; 46 | } 47 | 48 | QWebSettings::WebAttribute attr = attributes_by_name[name]; 49 | if (val != "reset") 50 | page()->settings()->setAttribute(attr, val != "false"); 51 | else 52 | page()->settings()->resetAttribute(attr); 53 | 54 | finish(true); 55 | } 56 | -------------------------------------------------------------------------------- /src/SetAttribute.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class SetAttribute : public SocketCommand { 4 | Q_OBJECT; 5 | 6 | public: 7 | SetAttribute(WebPageManager*, QStringList&, QObject* parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/SetConfirmAction.cpp: -------------------------------------------------------------------------------- 1 | #include "SetConfirmAction.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | 5 | SetConfirmAction::SetConfirmAction(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} 6 | 7 | void SetConfirmAction::start() 8 | { 9 | page()->setConfirmAction(arguments()[0]); 10 | finish(true); 11 | } 12 | -------------------------------------------------------------------------------- /src/SetConfirmAction.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class SetConfirmAction : public SocketCommand { 4 | Q_OBJECT; 5 | 6 | public: 7 | SetConfirmAction(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/SetCookie.cpp: -------------------------------------------------------------------------------- 1 | #include "SetCookie.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "NetworkCookieJar.h" 5 | #include 6 | 7 | SetCookie::SetCookie(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} 8 | 9 | void SetCookie::start() 10 | { 11 | QList cookies = QNetworkCookie::parseCookies(arguments()[0].toLatin1()); 12 | NetworkCookieJar *jar = manager()->cookieJar(); 13 | jar->overwriteCookies(cookies); 14 | finish(true); 15 | } 16 | -------------------------------------------------------------------------------- /src/SetCookie.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class SetCookie : public SocketCommand { 4 | Q_OBJECT; 5 | 6 | public: 7 | SetCookie(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/SetHtml.cpp: -------------------------------------------------------------------------------- 1 | #include "SetHtml.h" 2 | #include "WebPage.h" 3 | #include 4 | 5 | SetHtml::SetHtml(WebPageManager *manager, QStringList &arguments, QObject *parent) 6 | : SocketCommand(manager, arguments, parent) 7 | { } 8 | 9 | void SetHtml::start() { 10 | if (arguments().size() > 1) 11 | page()->currentFrame()->setHtml(arguments()[0], QUrl(arguments()[1])); 12 | else 13 | page()->currentFrame()->setHtml(arguments()[0]); 14 | finish(true); 15 | } 16 | -------------------------------------------------------------------------------- /src/SetHtml.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class SetHtml : public SocketCommand { 4 | Q_OBJECT; 5 | 6 | public: 7 | SetHtml(WebPageManager*, QStringList&, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/SetPromptAction.cpp: -------------------------------------------------------------------------------- 1 | #include "SetPromptAction.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | 5 | SetPromptAction::SetPromptAction(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} 6 | 7 | void SetPromptAction::start() 8 | { 9 | page()->setPromptAction(arguments()[0]); 10 | finish(true); 11 | } 12 | -------------------------------------------------------------------------------- /src/SetPromptAction.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class SetPromptAction : public SocketCommand { 4 | Q_OBJECT; 5 | 6 | public: 7 | SetPromptAction(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/SetPromptText.cpp: -------------------------------------------------------------------------------- 1 | #include "SetPromptText.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | 5 | SetPromptText::SetPromptText(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} 6 | 7 | void SetPromptText::start() 8 | { 9 | page()->setPromptText(arguments()[0]); 10 | finish(true); 11 | } 12 | -------------------------------------------------------------------------------- /src/SetPromptText.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class SetPromptText : public SocketCommand { 4 | Q_OBJECT; 5 | 6 | public: 7 | SetPromptText(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/SetProxy.cpp: -------------------------------------------------------------------------------- 1 | #include "SetProxy.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "NetworkAccessManager.h" 5 | #include 6 | 7 | SetProxy::SetProxy(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) {} 8 | 9 | void SetProxy::start() 10 | { 11 | // default to empty proxy 12 | QNetworkProxy proxy; 13 | 14 | if (arguments().size() > 0) 15 | proxy = QNetworkProxy(QNetworkProxy::HttpProxy, 16 | arguments()[0], 17 | (quint16)(arguments()[1].toInt()), 18 | arguments()[2], 19 | arguments()[3]); 20 | 21 | manager()->networkAccessManager()->setProxy(proxy); 22 | finish(true); 23 | } 24 | -------------------------------------------------------------------------------- /src/SetProxy.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class SetProxy : public SocketCommand { 4 | Q_OBJECT; 5 | 6 | public: 7 | SetProxy(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/SetSkipImageLoading.cpp: -------------------------------------------------------------------------------- 1 | #include "SetSkipImageLoading.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | 5 | SetSkipImageLoading::SetSkipImageLoading(WebPageManager *manager, QStringList &arguments, QObject *parent) : 6 | SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void SetSkipImageLoading::start() { 10 | page()->setSkipImageLoading(arguments().contains("true")); 11 | finish(true); 12 | } 13 | -------------------------------------------------------------------------------- /src/SetSkipImageLoading.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class SetSkipImageLoading : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | SetSkipImageLoading(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/SetTimeout.cpp: -------------------------------------------------------------------------------- 1 | #include "SetTimeout.h" 2 | #include "WebPageManager.h" 3 | #include "ErrorMessage.h" 4 | 5 | SetTimeout::SetTimeout(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 6 | } 7 | 8 | void SetTimeout::start() { 9 | QString timeoutString = arguments()[0]; 10 | bool ok; 11 | int timeout = timeoutString.toInt(&ok); 12 | 13 | if (ok) { 14 | manager()->setTimeout(timeout); 15 | finish(true); 16 | } else { 17 | finish(false, new ErrorMessage("Invalid value for timeout")); 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/SetTimeout.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class SetTimeout : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | SetTimeout(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/SetUrlBlacklist.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "SetUrlBlacklist.h" 3 | #include "WebPageManager.h" 4 | #include "WebPage.h" 5 | #include "NetworkAccessManager.h" 6 | 7 | SetUrlBlacklist::SetUrlBlacklist(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 8 | } 9 | 10 | void SetUrlBlacklist::start() { 11 | NetworkAccessManager* networkAccessManager = manager()->networkAccessManager(); 12 | networkAccessManager->setUrlBlacklist(arguments()); 13 | finish(true); 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/SetUrlBlacklist.h: -------------------------------------------------------------------------------- 1 | 2 | #include "SocketCommand.h" 3 | 4 | class SetUrlBlacklist : public SocketCommand { 5 | Q_OBJECT 6 | 7 | public: 8 | SetUrlBlacklist(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); 9 | virtual void start(); 10 | }; 11 | 12 | -------------------------------------------------------------------------------- /src/SocketCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | 5 | SocketCommand::SocketCommand(WebPageManager *manager, QStringList &arguments, QObject *parent) : Command(parent) { 6 | m_manager = manager; 7 | m_arguments = arguments; 8 | } 9 | 10 | WebPage *SocketCommand::page() const { 11 | return m_manager->currentPage(); 12 | } 13 | 14 | const QStringList &SocketCommand::arguments() const { 15 | return m_arguments; 16 | } 17 | 18 | WebPageManager *SocketCommand::manager() const { 19 | return m_manager; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/SocketCommand.h: -------------------------------------------------------------------------------- 1 | #ifndef SOCKET_COMMAND_H 2 | #define SOCKET_COMMAND_H 3 | 4 | #include 5 | #include 6 | #include "Command.h" 7 | 8 | class WebPage; 9 | class WebPageManager; 10 | class Response; 11 | 12 | class SocketCommand : public Command { 13 | Q_OBJECT 14 | 15 | public: 16 | SocketCommand(WebPageManager *, QStringList &arguments, QObject *parent = 0); 17 | 18 | protected: 19 | WebPage *page() const; 20 | const QStringList &arguments() const; 21 | WebPageManager *manager() const; 22 | 23 | private: 24 | QStringList m_arguments; 25 | WebPageManager *m_manager; 26 | 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/Source.cpp: -------------------------------------------------------------------------------- 1 | #include "Source.h" 2 | #include "NetworkAccessManager.h" 3 | #include "WebPageManager.h" 4 | #include "WebPage.h" 5 | 6 | Source::Source(WebPageManager *manager, QStringList &arguments, 7 | QObject *parent) 8 | : SocketCommand(manager, arguments, parent) { 9 | } 10 | 11 | void Source::start() { 12 | NetworkAccessManager* accessManager = manager()->networkAccessManager(); 13 | QNetworkRequest request(page()->currentFrame()->url()); 14 | reply = accessManager->get(request); 15 | 16 | connect(reply, SIGNAL(finished()), this, SLOT(sourceLoaded())); 17 | } 18 | 19 | void Source::sourceLoaded() { 20 | finish(true, reply->readAll()); 21 | } 22 | -------------------------------------------------------------------------------- /src/Source.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class QNetworkReply; 4 | 5 | class Source : public SocketCommand { 6 | Q_OBJECT 7 | 8 | public: 9 | Source(WebPageManager*, QStringList&, QObject *parent = 0); 10 | virtual void start(); 11 | 12 | public slots: 13 | void sourceLoaded(); 14 | 15 | private: 16 | QNetworkReply *reply; 17 | }; 18 | 19 | -------------------------------------------------------------------------------- /src/Status.cpp: -------------------------------------------------------------------------------- 1 | #include "Status.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include 5 | 6 | Status::Status(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void Status::start() { 10 | int status = page()->getLastStatus(); 11 | finish(true, QString::number(status)); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/Status.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class Status : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | Status(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/TimeoutCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "TimeoutCommand.h" 2 | #include "Command.h" 3 | #include "WebPageManager.h" 4 | #include "WebPage.h" 5 | #include "ErrorMessage.h" 6 | #include 7 | #include 8 | 9 | TimeoutCommand::TimeoutCommand(Command *command, WebPageManager *manager, QObject *parent) : Command(parent) { 10 | m_command = command; 11 | m_manager = manager; 12 | m_timer = new QTimer(this); 13 | m_timer->setSingleShot(true); 14 | m_command->setParent(this); 15 | connect(m_timer, SIGNAL(timeout()), this, SLOT(commandTimeout())); 16 | connect(m_manager, SIGNAL(loadStarted()), this, SLOT(pageLoadingFromCommand())); 17 | } 18 | 19 | void TimeoutCommand::start() { 20 | QApplication::processEvents(); 21 | if (m_manager->isLoading()) { 22 | m_manager->logger() << this->toString() << "waiting for load to finish"; 23 | connect(m_manager, SIGNAL(pageFinished(bool)), this, SLOT(pendingLoadFinished(bool))); 24 | startTimeout(); 25 | } else { 26 | startCommand(); 27 | } 28 | } 29 | 30 | void TimeoutCommand::startCommand() { 31 | connect(m_command, SIGNAL(finished(Response *)), this, SLOT(commandFinished(Response *))); 32 | m_command->start(); 33 | } 34 | 35 | void TimeoutCommand::startTimeout() { 36 | int timeout = m_manager->getTimeout(); 37 | if (timeout > 0) { 38 | m_timer->start(timeout * 1000); 39 | } 40 | } 41 | 42 | void TimeoutCommand::pendingLoadFinished(bool success) { 43 | disconnect(m_manager, SIGNAL(pageFinished(bool)), this, SLOT(pendingLoadFinished(bool))); 44 | if (success) { 45 | startCommand(); 46 | } else { 47 | disconnect(m_timer, SIGNAL(timeout()), this, SLOT(commandTimeout())); 48 | disconnect(m_manager, SIGNAL(loadStarted()), this, SLOT(pageLoadingFromCommand())); 49 | finish(false, new ErrorMessage(m_manager->currentPage()->failureString())); 50 | } 51 | } 52 | 53 | void TimeoutCommand::pageLoadingFromCommand() { 54 | startTimeout(); 55 | } 56 | 57 | void TimeoutCommand::commandTimeout() { 58 | disconnect(m_manager, SIGNAL(loadStarted()), this, SLOT(pageLoadingFromCommand())); 59 | disconnect(m_manager, SIGNAL(pageFinished(bool)), this, SLOT(pendingLoadFinished(bool))); 60 | disconnect(m_command, SIGNAL(finished(Response *)), this, SLOT(commandFinished(Response *))); 61 | m_manager->currentPage()->triggerAction(QWebPage::Stop); 62 | QString message = QString("Request timed out after %1 second(s)").arg(m_manager->getTimeout()); 63 | finish(false, new ErrorMessage("TimeoutError", message)); 64 | } 65 | 66 | void TimeoutCommand::commandFinished(Response *response) { 67 | disconnect(m_timer, SIGNAL(timeout()), this, SLOT(commandTimeout())); 68 | disconnect(m_manager, SIGNAL(loadStarted()), this, SLOT(pageLoadingFromCommand())); 69 | emit finished(response); 70 | } 71 | 72 | -------------------------------------------------------------------------------- /src/TimeoutCommand.h: -------------------------------------------------------------------------------- 1 | #include "Command.h" 2 | #include 3 | #include 4 | 5 | class Response; 6 | class WebPageManager; 7 | class QTimer; 8 | 9 | /* Decorates a command with a timeout. 10 | * 11 | * If the timeout, using a QTimer is reached before 12 | * the command is finished, the load page load will 13 | * be stopped and failure response will be issued. 14 | * 15 | */ 16 | class TimeoutCommand : public Command { 17 | Q_OBJECT 18 | 19 | public: 20 | TimeoutCommand(Command *command, WebPageManager *page, QObject *parent = 0); 21 | virtual void start(); 22 | 23 | public slots: 24 | void commandTimeout(); 25 | void commandFinished(Response *response); 26 | void pageLoadingFromCommand(); 27 | void pendingLoadFinished(bool); 28 | 29 | protected: 30 | void startCommand(); 31 | void startTimeout(); 32 | 33 | private: 34 | WebPageManager *m_manager; 35 | QTimer *m_timer; 36 | Command *m_command; 37 | }; 38 | 39 | -------------------------------------------------------------------------------- /src/Title.cpp: -------------------------------------------------------------------------------- 1 | #include "Title.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "NetworkAccessManager.h" 5 | 6 | Title::Title(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void Title::start() { 10 | finish(true, page()->currentFrame()->title()); 11 | } 12 | -------------------------------------------------------------------------------- /src/Title.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class Title : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | Title(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/UnsupportedContentHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "UnsupportedContentHandler.h" 2 | #include "WebPage.h" 3 | #include 4 | 5 | UnsupportedContentHandler::UnsupportedContentHandler(WebPage *page, QNetworkReply *reply, QObject *parent) : QObject(parent) { 6 | m_page = page; 7 | m_reply = reply; 8 | } 9 | 10 | void UnsupportedContentHandler::renderNonHtmlContent() { 11 | QByteArray text = m_reply->readAll(); 12 | m_page->mainFrame()->setContent(text, QString("text/plain"), m_reply->url()); 13 | m_page->unsupportedContentFinishedReply(m_reply); 14 | this->deleteLater(); 15 | } 16 | 17 | void UnsupportedContentHandler::waitForReplyToFinish() { 18 | connect(m_reply, SIGNAL(finished()), this, SLOT(replyFinished())); 19 | } 20 | 21 | void UnsupportedContentHandler::replyFinished() { 22 | renderNonHtmlContent(); 23 | } 24 | -------------------------------------------------------------------------------- /src/UnsupportedContentHandler.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class WebPage; 4 | class QNetworkReply; 5 | 6 | class UnsupportedContentHandler : public QObject { 7 | Q_OBJECT 8 | 9 | public: 10 | UnsupportedContentHandler(WebPage *page, QNetworkReply *reply, QObject *parent = 0); 11 | void waitForReplyToFinish(); 12 | void renderNonHtmlContent(); 13 | 14 | public slots: 15 | void replyFinished(); 16 | 17 | private: 18 | WebPage *m_page; 19 | QNetworkReply *m_reply; 20 | }; 21 | -------------------------------------------------------------------------------- /src/Version.cpp: -------------------------------------------------------------------------------- 1 | #include "Version.h" 2 | #include "WebPage.h" 3 | 4 | Version::Version(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 5 | } 6 | 7 | void Version::start() { 8 | QString result = 9 | QString("Qt: ") + QT_VERSION_STR + 10 | QString("\nWebKit: ") + qWebKitVersion() + 11 | QString("\nQtWebKit: ") + QTWEBKIT_VERSION_STR; 12 | finish(true, result); 13 | } 14 | -------------------------------------------------------------------------------- /src/Version.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class Version : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | Version(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/Visit.cpp: -------------------------------------------------------------------------------- 1 | #include "Visit.h" 2 | #include "SocketCommand.h" 3 | #include "WebPage.h" 4 | #include "WebPageManager.h" 5 | 6 | Visit::Visit(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void Visit::start() { 10 | QUrl requestedUrl = QUrl::fromEncoded(arguments()[0].toUtf8(), QUrl::TolerantMode); 11 | page()->currentFrame()->load(QUrl(requestedUrl)); 12 | finish(true); 13 | } 14 | -------------------------------------------------------------------------------- /src/Visit.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class Visit : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | Visit(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/WebPage.cpp: -------------------------------------------------------------------------------- 1 | #include "WebPage.h" 2 | #include "WebPageManager.h" 3 | #include "JavascriptInvocation.h" 4 | #include "NetworkAccessManager.h" 5 | #include "NetworkCookieJar.h" 6 | #include "UnsupportedContentHandler.h" 7 | #include "InvocationResult.h" 8 | #include "NetworkReplyProxy.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | WebPage::WebPage(WebPageManager *manager, QObject *parent) : QWebPage(parent) { 16 | m_loading = false; 17 | m_failed = false; 18 | m_manager = manager; 19 | m_uuid = QUuid::createUuid().toString(); 20 | 21 | setForwardUnsupportedContent(true); 22 | loadJavascript(); 23 | setUserStylesheet(); 24 | 25 | m_confirm = true; 26 | m_prompt = false; 27 | m_prompt_text = QString(); 28 | this->setCustomNetworkAccessManager(); 29 | 30 | connect(this, SIGNAL(loadStarted()), this, SLOT(loadStarted())); 31 | connect(this, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool))); 32 | connect(this, SIGNAL(frameCreated(QWebFrame *)), 33 | this, SLOT(frameCreated(QWebFrame *))); 34 | connect(this, SIGNAL(unsupportedContent(QNetworkReply*)), 35 | this, SLOT(handleUnsupportedContent(QNetworkReply*))); 36 | resetWindowSize(); 37 | 38 | settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, true); 39 | } 40 | 41 | void WebPage::resetWindowSize() { 42 | this->setViewportSize(QSize(1680, 1050)); 43 | this->settings()->setAttribute(QWebSettings::LocalStorageDatabaseEnabled, true); 44 | } 45 | 46 | void WebPage::resetLocalStorage() { 47 | this->currentFrame()->evaluateJavaScript("localStorage.clear()"); 48 | } 49 | 50 | void WebPage::setCustomNetworkAccessManager() { 51 | setNetworkAccessManager(m_manager->networkAccessManager()); 52 | connect(networkAccessManager(), SIGNAL(sslErrors(QNetworkReply *, QList)), 53 | SLOT(handleSslErrorsForReply(QNetworkReply *, QList))); 54 | connect(networkAccessManager(), SIGNAL(requestCreated(QByteArray &, QNetworkReply *)), 55 | SIGNAL(requestCreated(QByteArray &, QNetworkReply *))); 56 | connect(networkAccessManager(), SIGNAL(finished(QUrl &, QNetworkReply *)), 57 | SLOT(replyFinished(QUrl &, QNetworkReply *))); 58 | } 59 | 60 | void WebPage::replyFinished(QUrl &requestedUrl, QNetworkReply *reply) { 61 | NetworkReplyProxy *proxy = qobject_cast(reply); 62 | setFrameProperties(mainFrame(), requestedUrl, proxy); 63 | foreach(QWebFrame *frame, mainFrame()->childFrames()) 64 | setFrameProperties(frame, requestedUrl, proxy); 65 | } 66 | 67 | void WebPage::setFrameProperties(QWebFrame *frame, QUrl &requestedUrl, NetworkReplyProxy *reply) { 68 | if (frame->requestedUrl() == requestedUrl) { 69 | int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 70 | frame->setProperty("statusCode", statusCode); 71 | QStringList headers; 72 | foreach(QNetworkReply::RawHeaderPair header, reply->rawHeaderPairs()) 73 | headers << header.first+": "+header.second; 74 | frame->setProperty("headers", headers); 75 | frame->setProperty("body", reply->data()); 76 | QVariant contentMimeType = reply->header(QNetworkRequest::ContentTypeHeader); 77 | frame->setProperty("contentType", contentMimeType); 78 | } 79 | } 80 | 81 | void WebPage::unsupportedContentFinishedReply(QNetworkReply *reply) { 82 | m_manager->replyFinished(reply); 83 | } 84 | 85 | void WebPage::loadJavascript() { 86 | QResource javascript(":/capybara.js"); 87 | if (javascript.isCompressed()) { 88 | QByteArray uncompressedBytes(qUncompress(javascript.data(), javascript.size())); 89 | m_capybaraJavascript = QString(uncompressedBytes); 90 | } else { 91 | char * javascriptString = new char[javascript.size() + 1]; 92 | strcpy(javascriptString, (const char *)javascript.data()); 93 | javascriptString[javascript.size()] = 0; 94 | m_capybaraJavascript = javascriptString; 95 | } 96 | } 97 | 98 | void WebPage::setUserStylesheet() { 99 | QString data = QString("*, :before, :after { font-family: 'Arial' ! important; }").toUtf8().toBase64(); 100 | QUrl url = QUrl(QString("data:text/css;charset=utf-8;base64,") + data); 101 | settings()->setUserStyleSheetUrl(url); 102 | } 103 | 104 | QString WebPage::userAgentForUrl(const QUrl &url ) const { 105 | if (!m_userAgent.isEmpty()) { 106 | return m_userAgent; 107 | } else { 108 | return QWebPage::userAgentForUrl(url); 109 | } 110 | } 111 | 112 | QVariantList WebPage::consoleMessages() { 113 | return m_consoleMessages; 114 | } 115 | 116 | QVariantList WebPage::alertMessages() { 117 | return m_alertMessages; 118 | } 119 | 120 | QVariantList WebPage::confirmMessages() { 121 | return m_confirmMessages; 122 | } 123 | 124 | QVariantList WebPage::promptMessages() { 125 | return m_promptMessages; 126 | } 127 | 128 | void WebPage::setUserAgent(QString userAgent) { 129 | m_userAgent = userAgent; 130 | } 131 | 132 | void WebPage::frameCreated(QWebFrame * frame) { 133 | connect(frame, SIGNAL(javaScriptWindowObjectCleared()), 134 | this, SLOT(injectJavascriptHelpers())); 135 | } 136 | 137 | void WebPage::injectJavascriptHelpers() { 138 | QWebFrame* frame = qobject_cast(QObject::sender()); 139 | frame->evaluateJavaScript(m_capybaraJavascript); 140 | } 141 | 142 | bool WebPage::shouldInterruptJavaScript() { 143 | return false; 144 | } 145 | 146 | InvocationResult WebPage::invokeCapybaraFunction(const char *name, bool allowUnattached, const QStringList &arguments) { 147 | QString qname(name); 148 | JavascriptInvocation invocation(qname, allowUnattached, arguments, this); 149 | return invocation.invoke(currentFrame()); 150 | } 151 | 152 | InvocationResult WebPage::invokeCapybaraFunction(QString &name, bool allowUnattached, const QStringList &arguments) { 153 | return invokeCapybaraFunction(name.toLatin1().data(), allowUnattached, arguments); 154 | } 155 | 156 | void WebPage::javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID) { 157 | QVariantMap m; 158 | m["message"] = message; 159 | QString fullMessage = QString(message); 160 | if (!sourceID.isEmpty()) { 161 | fullMessage = sourceID + "|" + QString::number(lineNumber) + "|" + fullMessage; 162 | m["source"] = sourceID; 163 | m["line_number"] = lineNumber; 164 | } 165 | m_consoleMessages.append(m); 166 | m_manager->logger() << qPrintable(fullMessage); 167 | } 168 | 169 | void WebPage::javaScriptAlert(QWebFrame *frame, const QString &message) { 170 | Q_UNUSED(frame); 171 | m_alertMessages.append(message); 172 | m_manager->logger() << "ALERT:" << qPrintable(message); 173 | } 174 | 175 | bool WebPage::javaScriptConfirm(QWebFrame *frame, const QString &message) { 176 | Q_UNUSED(frame); 177 | m_confirmMessages.append(message); 178 | return m_confirm; 179 | } 180 | 181 | bool WebPage::javaScriptPrompt(QWebFrame *frame, const QString &message, const QString &defaultValue, QString *result) { 182 | Q_UNUSED(frame) 183 | m_promptMessages.append(message); 184 | if (m_prompt) { 185 | if (m_prompt_text.isNull()) { 186 | *result = defaultValue; 187 | } else { 188 | *result = m_prompt_text; 189 | } 190 | } 191 | return m_prompt; 192 | } 193 | 194 | void WebPage::loadStarted() { 195 | m_loading = true; 196 | m_errorPageMessage = QString(); 197 | } 198 | 199 | void WebPage::loadFinished(bool success) { 200 | Q_UNUSED(success); 201 | m_loading = false; 202 | emit pageFinished(!m_failed); 203 | m_failed = false; 204 | } 205 | 206 | bool WebPage::isLoading() const { 207 | return m_loading; 208 | } 209 | 210 | QString WebPage::failureString() { 211 | QString message = QString("Unable to load URL: ") + currentFrame()->requestedUrl().toString(); 212 | if (m_errorPageMessage.isEmpty()) 213 | return message; 214 | else 215 | return message + m_errorPageMessage; 216 | } 217 | 218 | void WebPage::mouseEvent(QEvent::Type type, const QPoint &position, Qt::MouseButton button) { 219 | m_mousePosition = position; 220 | QMouseEvent event(type, position, button, button, Qt::NoModifier); 221 | QApplication::sendEvent(this, &event); 222 | } 223 | 224 | bool WebPage::clickTest(QWebElement element, int absoluteX, int absoluteY) { 225 | QPoint mousePos(absoluteX, absoluteY); 226 | m_mousePosition = mousePos; 227 | QWebHitTestResult res = mainFrame()->hitTestContent(mousePos); 228 | return res.frame() == element.webFrame(); 229 | } 230 | 231 | bool WebPage::render(const QString &fileName, const QSize &minimumSize) { 232 | QFileInfo fileInfo(fileName); 233 | QDir dir; 234 | dir.mkpath(fileInfo.absolutePath()); 235 | 236 | QSize viewportSize = this->viewportSize(); 237 | this->setViewportSize(minimumSize); 238 | QSize pageSize = this->mainFrame()->contentsSize(); 239 | if (pageSize.isEmpty()) { 240 | return false; 241 | } 242 | 243 | QImage buffer(pageSize, QImage::Format_ARGB32); 244 | buffer.fill(qRgba(255, 255, 255, 0)); 245 | 246 | QPainter p(&buffer); 247 | p.setRenderHint( QPainter::Antialiasing, true); 248 | p.setRenderHint( QPainter::TextAntialiasing, true); 249 | p.setRenderHint( QPainter::SmoothPixmapTransform, true); 250 | 251 | this->setViewportSize(pageSize); 252 | this->mainFrame()->render(&p); 253 | 254 | //QImage pointer = QImage(":/pointer.png"); 255 | //p.drawImage(m_mousePosition, pointer); 256 | 257 | p.end(); 258 | this->setViewportSize(viewportSize); 259 | 260 | return buffer.save(fileName); 261 | } 262 | 263 | QString WebPage::chooseFile(QWebFrame *parentFrame, const QString &suggestedFile) { 264 | Q_UNUSED(parentFrame); 265 | Q_UNUSED(suggestedFile); 266 | 267 | return getAttachedFileNames().first(); 268 | } 269 | 270 | bool WebPage::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) { 271 | if (extension == ChooseMultipleFilesExtension) { 272 | static_cast(output)->fileNames = getAttachedFileNames(); 273 | return true; 274 | } 275 | else if (extension == QWebPage::ErrorPageExtension) { 276 | ErrorPageExtensionOption *errorOption = (ErrorPageExtensionOption*) option; 277 | m_errorPageMessage = " because of error loading " + errorOption->url.toString() + ": " + errorOption->errorString; 278 | m_failed = true; 279 | return false; 280 | } 281 | return false; 282 | } 283 | 284 | QStringList WebPage::getAttachedFileNames() { 285 | return currentFrame()->evaluateJavaScript(QString("Capybara.attachedFiles")).toStringList(); 286 | } 287 | 288 | void WebPage::handleSslErrorsForReply(QNetworkReply *reply, const QList &errors) { 289 | Q_UNUSED(errors); 290 | if (m_manager->ignoreSslErrors()) 291 | reply->ignoreSslErrors(); 292 | } 293 | 294 | void WebPage::setSkipImageLoading(bool skip) { 295 | settings()->setAttribute(QWebSettings::AutoLoadImages, !skip); 296 | } 297 | 298 | int WebPage::getLastStatus() { 299 | return currentFrame()->property("statusCode").toInt(); 300 | } 301 | 302 | QStringList WebPage::pageHeaders() { 303 | return currentFrame()->property("headers").toStringList(); 304 | } 305 | 306 | QByteArray WebPage::body() { 307 | return currentFrame()->property("body").toByteArray(); 308 | } 309 | 310 | QString WebPage::contentType() { 311 | return currentFrame()->property("contentType").toString(); 312 | } 313 | 314 | void WebPage::handleUnsupportedContent(QNetworkReply *reply) { 315 | QVariant contentMimeType = reply->header(QNetworkRequest::ContentTypeHeader); 316 | if(!contentMimeType.isNull()) { 317 | triggerAction(QWebPage::Stop); 318 | UnsupportedContentHandler *handler = new UnsupportedContentHandler(this, reply); 319 | if (reply->isFinished()) 320 | handler->renderNonHtmlContent(); 321 | else 322 | handler->waitForReplyToFinish(); 323 | } 324 | } 325 | 326 | bool WebPage::supportsExtension(Extension extension) const { 327 | if (extension == ErrorPageExtension) 328 | return true; 329 | else if (extension == ChooseMultipleFilesExtension) 330 | return true; 331 | else 332 | return false; 333 | } 334 | 335 | QWebPage *WebPage::createWindow(WebWindowType type) { 336 | Q_UNUSED(type); 337 | return m_manager->createPage(this); 338 | } 339 | 340 | QString WebPage::uuid() { 341 | return m_uuid; 342 | } 343 | 344 | QString WebPage::getWindowName() { 345 | QVariant windowName = mainFrame()->evaluateJavaScript("window.name"); 346 | 347 | if (windowName.isValid()) 348 | return windowName.toString(); 349 | else 350 | return ""; 351 | } 352 | 353 | bool WebPage::matchesWindowSelector(QString selector) { 354 | return (selector == getWindowName() || 355 | selector == mainFrame()->title() || 356 | selector == mainFrame()->url().toString() || 357 | selector == uuid()); 358 | } 359 | 360 | void WebPage::setFocus() { 361 | m_manager->setCurrentPage(this); 362 | } 363 | 364 | void WebPage::setConfirmAction(QString action) { 365 | m_confirm = (action == "Yes"); 366 | } 367 | 368 | void WebPage::setPromptAction(QString action) { 369 | m_prompt = (action == "Yes"); 370 | } 371 | 372 | void WebPage::setPromptText(QString text) { 373 | m_prompt_text = text; 374 | } 375 | 376 | -------------------------------------------------------------------------------- /src/WebPage.h: -------------------------------------------------------------------------------- 1 | #ifndef _WEBPAGE_H 2 | #define _WEBPAGE_H 3 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 4 | #include 5 | #else 6 | #include 7 | #endif 8 | #include 9 | 10 | class WebPageManager; 11 | class InvocationResult; 12 | class NetworkReplyProxy; 13 | 14 | class WebPage : public QWebPage { 15 | Q_OBJECT 16 | 17 | public: 18 | WebPage(WebPageManager *, QObject *parent = 0); 19 | InvocationResult invokeCapybaraFunction(const char *name, bool allowUnattached, const QStringList &arguments); 20 | InvocationResult invokeCapybaraFunction(QString &name, bool allowUnattached, const QStringList &arguments); 21 | QString failureString(); 22 | QString userAgentForUrl(const QUrl &url ) const; 23 | void setUserAgent(QString userAgent); 24 | void setConfirmAction(QString action); 25 | void setPromptAction(QString action); 26 | void setPromptText(QString action); 27 | int getLastStatus(); 28 | void setCustomNetworkAccessManager(); 29 | bool render(const QString &fileName, const QSize &minimumSize); 30 | virtual bool extension (Extension extension, const ExtensionOption *option=0, ExtensionReturn *output=0); 31 | void setSkipImageLoading(bool skip); 32 | QVariantList consoleMessages(); 33 | QVariantList alertMessages(); 34 | QVariantList confirmMessages(); 35 | QVariantList promptMessages(); 36 | void resetWindowSize(); 37 | void resetLocalStorage(); 38 | QWebPage *createWindow(WebWindowType type); 39 | QString uuid(); 40 | QString getWindowName(); 41 | bool matchesWindowSelector(QString); 42 | void setFocus(); 43 | void unsupportedContentFinishedReply(QNetworkReply *reply); 44 | QStringList pageHeaders(); 45 | QByteArray body(); 46 | QString contentType(); 47 | void mouseEvent(QEvent::Type type, const QPoint &position, Qt::MouseButton button); 48 | bool clickTest(QWebElement element, int absoluteX, int absoluteY); 49 | 50 | public slots: 51 | bool shouldInterruptJavaScript(); 52 | void injectJavascriptHelpers(); 53 | void loadStarted(); 54 | void loadFinished(bool); 55 | bool isLoading() const; 56 | void frameCreated(QWebFrame *); 57 | void handleSslErrorsForReply(QNetworkReply *reply, const QList &); 58 | void handleUnsupportedContent(QNetworkReply *reply); 59 | void replyFinished(QUrl &, QNetworkReply *); 60 | 61 | signals: 62 | void pageFinished(bool); 63 | void requestCreated(QByteArray &url, QNetworkReply *reply); 64 | void replyFinished(QNetworkReply *reply); 65 | 66 | protected: 67 | virtual void javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID); 68 | virtual void javaScriptAlert(QWebFrame *frame, const QString &message); 69 | virtual bool javaScriptConfirm(QWebFrame *frame, const QString &message); 70 | virtual bool javaScriptPrompt(QWebFrame *frame, const QString &message, const QString &defaultValue, QString *result); 71 | virtual QString chooseFile(QWebFrame * parentFrame, const QString &suggestedFile); 72 | virtual bool supportsExtension(Extension extension) const; 73 | 74 | private: 75 | QString m_capybaraJavascript; 76 | QString m_userAgent; 77 | bool m_loading; 78 | bool m_failed; 79 | QStringList getAttachedFileNames(); 80 | void loadJavascript(); 81 | void setUserStylesheet(); 82 | bool m_confirm; 83 | bool m_prompt; 84 | QVariantList m_consoleMessages; 85 | QVariantList m_alertMessages; 86 | QVariantList m_confirmMessages; 87 | QString m_prompt_text; 88 | QVariantList m_promptMessages; 89 | QString m_uuid; 90 | WebPageManager *m_manager; 91 | QString m_errorPageMessage; 92 | void setFrameProperties(QWebFrame *, QUrl &, NetworkReplyProxy *); 93 | QPoint m_mousePosition; 94 | }; 95 | 96 | #endif //_WEBPAGE_H 97 | 98 | -------------------------------------------------------------------------------- /src/WebPageManager.cpp: -------------------------------------------------------------------------------- 1 | #include "WebPageManager.h" 2 | #include "WebPage.h" 3 | #include "NetworkCookieJar.h" 4 | #include "NetworkAccessManager.h" 5 | 6 | WebPageManager::WebPageManager(QObject *parent) : QObject(parent) { 7 | m_ignoreSslErrors = false; 8 | m_cookieJar = new NetworkCookieJar(this); 9 | m_success = true; 10 | m_loggingEnabled = false; 11 | m_ignoredOutput = new QFile(this); 12 | m_timeout = -1; 13 | m_networkAccessManager = new NetworkAccessManager(this); 14 | m_networkAccessManager->setCookieJar(m_cookieJar); 15 | createPage(this)->setFocus(); 16 | } 17 | 18 | NetworkAccessManager *WebPageManager::networkAccessManager() { 19 | return m_networkAccessManager; 20 | } 21 | 22 | void WebPageManager::append(WebPage *value) { 23 | m_pages.append(value); 24 | } 25 | 26 | QList WebPageManager::pages() const { 27 | return m_pages; 28 | } 29 | 30 | void WebPageManager::setCurrentPage(WebPage *page) { 31 | m_currentPage = page; 32 | } 33 | 34 | WebPage *WebPageManager::currentPage() const { 35 | return m_currentPage; 36 | } 37 | 38 | WebPage *WebPageManager::createPage(QObject *parent) { 39 | WebPage *page = new WebPage(this, parent); 40 | connect(page, SIGNAL(loadStarted()), 41 | this, SLOT(emitLoadStarted())); 42 | connect(page, SIGNAL(pageFinished(bool)), 43 | this, SLOT(setPageStatus(bool))); 44 | connect(page, SIGNAL(requestCreated(QByteArray &, QNetworkReply *)), 45 | this, SLOT(requestCreated(QByteArray &, QNetworkReply *))); 46 | append(page); 47 | return page; 48 | } 49 | 50 | void WebPageManager::emitLoadStarted() { 51 | if (m_started.empty()) { 52 | logger() << "Load started"; 53 | emit loadStarted(); 54 | } 55 | m_started += qobject_cast(sender()); 56 | } 57 | 58 | void WebPageManager::requestCreated(QByteArray &url, QNetworkReply *reply) { 59 | logger() << "Started request to" << url; 60 | if (reply->isFinished()) 61 | replyFinished(reply); 62 | else { 63 | connect(reply, SIGNAL(finished()), SLOT(handleReplyFinished())); 64 | } 65 | } 66 | 67 | void WebPageManager::handleReplyFinished() { 68 | QNetworkReply *reply = qobject_cast(sender()); 69 | disconnect(reply, SIGNAL(finished()), this, SLOT(handleReplyFinished())); 70 | replyFinished(reply); 71 | } 72 | 73 | void WebPageManager::replyFinished(QNetworkReply *reply) { 74 | int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 75 | logger() << "Received" << status << "from" << reply->url().toString(); 76 | } 77 | 78 | void WebPageManager::setPageStatus(bool success) { 79 | logger() << "Page finished with" << success; 80 | m_started.remove(qobject_cast(sender())); 81 | m_success = success && m_success; 82 | if (m_started.empty()) { 83 | emitPageFinished(); 84 | } 85 | } 86 | 87 | void WebPageManager::emitPageFinished() { 88 | logger() << "Load finished"; 89 | emit pageFinished(m_success); 90 | m_success = true; 91 | } 92 | 93 | void WebPageManager::setIgnoreSslErrors(bool value) { 94 | m_ignoreSslErrors = value; 95 | } 96 | 97 | bool WebPageManager::ignoreSslErrors() { 98 | return m_ignoreSslErrors; 99 | } 100 | 101 | int WebPageManager::getTimeout() { 102 | return m_timeout; 103 | } 104 | 105 | void WebPageManager::setTimeout(int timeout) { 106 | m_timeout = timeout; 107 | } 108 | 109 | void WebPageManager::reset() { 110 | m_timeout = -1; 111 | m_cookieJar->clearCookies(); 112 | m_networkAccessManager->reset(); 113 | m_pages.first()->resetLocalStorage(); 114 | m_pages.first()->deleteLater(); 115 | m_pages.clear(); 116 | createPage(this)->setFocus(); 117 | } 118 | 119 | NetworkCookieJar *WebPageManager::cookieJar() { 120 | return m_cookieJar; 121 | } 122 | 123 | bool WebPageManager::isLoading() const { 124 | foreach(WebPage *page, pages()) { 125 | if (page->isLoading()) { 126 | return true; 127 | } 128 | } 129 | return false; 130 | } 131 | 132 | QDebug WebPageManager::logger() const { 133 | if (m_loggingEnabled) { 134 | return qCritical(); 135 | } else { 136 | return QDebug(m_ignoredOutput); 137 | } 138 | } 139 | 140 | void WebPageManager::enableLogging() { 141 | m_loggingEnabled = true; 142 | } 143 | -------------------------------------------------------------------------------- /src/WebPageManager.h: -------------------------------------------------------------------------------- 1 | #ifndef _WEBPAGEMANAGER_H 2 | #define _WEBPAGEMANAGER_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class WebPage; 11 | class NetworkCookieJar; 12 | class NetworkAccessManager; 13 | 14 | class WebPageManager : public QObject { 15 | Q_OBJECT 16 | 17 | public: 18 | WebPageManager(QObject *parent = 0); 19 | void append(WebPage *value); 20 | QList pages() const; 21 | void setCurrentPage(WebPage *); 22 | WebPage *currentPage() const; 23 | WebPage *createPage(QObject *parent); 24 | void setIgnoreSslErrors(bool); 25 | bool ignoreSslErrors(); 26 | void setTimeout(int); 27 | int getTimeout(); 28 | void reset(); 29 | NetworkCookieJar *cookieJar(); 30 | bool isLoading() const; 31 | QDebug logger() const; 32 | void enableLogging(); 33 | void replyFinished(QNetworkReply *reply); 34 | NetworkAccessManager *networkAccessManager(); 35 | 36 | public slots: 37 | void emitLoadStarted(); 38 | void setPageStatus(bool); 39 | void requestCreated(QByteArray &url, QNetworkReply *reply); 40 | void handleReplyFinished(); 41 | 42 | signals: 43 | void pageFinished(bool); 44 | void loadStarted(); 45 | 46 | private: 47 | void emitPageFinished(); 48 | static void handleDebugMessage(QtMsgType type, const char *message); 49 | 50 | QList m_pages; 51 | WebPage *m_currentPage; 52 | bool m_ignoreSslErrors; 53 | NetworkCookieJar *m_cookieJar; 54 | QSet m_started; 55 | bool m_success; 56 | bool m_loggingEnabled; 57 | QFile *m_ignoredOutput; 58 | int m_timeout; 59 | NetworkAccessManager *m_networkAccessManager; 60 | }; 61 | 62 | #endif // _WEBPAGEMANAGER_H 63 | 64 | -------------------------------------------------------------------------------- /src/WindowFocus.cpp: -------------------------------------------------------------------------------- 1 | #include "WindowFocus.h" 2 | #include "SocketCommand.h" 3 | #include "WebPage.h" 4 | #include "CommandFactory.h" 5 | #include "WebPageManager.h" 6 | #include "ErrorMessage.h" 7 | 8 | WindowFocus::WindowFocus(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 9 | } 10 | 11 | void WindowFocus::start() { 12 | focusWindow(arguments()[0]); 13 | } 14 | 15 | void WindowFocus::windowNotFound() { 16 | finish(false, new ErrorMessage("Unable to locate window.")); 17 | } 18 | 19 | void WindowFocus::success(WebPage *page) { 20 | page->setFocus(); 21 | finish(true); 22 | } 23 | 24 | void WindowFocus::focusWindow(QString selector) { 25 | foreach(WebPage *page, manager()->pages()) { 26 | if (page->matchesWindowSelector(selector)) { 27 | success(page); 28 | return; 29 | } 30 | } 31 | 32 | windowNotFound(); 33 | } 34 | -------------------------------------------------------------------------------- /src/WindowFocus.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class WindowFocus : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | WindowFocus(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | 10 | private: 11 | void success(WebPage *); 12 | void windowNotFound(); 13 | void focusWindow(QString); 14 | }; 15 | 16 | -------------------------------------------------------------------------------- /src/capybara.js: -------------------------------------------------------------------------------- 1 | Capybara = { 2 | nextIndex: 0, 3 | nodes: {}, 4 | attachedFiles: [], 5 | 6 | invoke: function () { 7 | try { 8 | return this[CapybaraInvocation.functionName].apply(this, CapybaraInvocation.arguments); 9 | } catch (e) { 10 | CapybaraInvocation.error = e; 11 | } 12 | }, 13 | 14 | findXpath: function (xpath) { 15 | return this.findXpathRelativeTo(document, xpath); 16 | }, 17 | 18 | findCss: function (selector) { 19 | return this.findCssRelativeTo(document, selector); 20 | }, 21 | 22 | findXpathWithin: function (index, xpath) { 23 | return this.findXpathRelativeTo(this.getNode(index), xpath); 24 | }, 25 | 26 | findCssWithin: function (index, selector) { 27 | return this.findCssRelativeTo(this.getNode(index), selector); 28 | }, 29 | 30 | findXpathRelativeTo: function (reference, xpath) { 31 | var iterator = document.evaluate(xpath, reference, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); 32 | var node; 33 | var results = []; 34 | while (node = iterator.iterateNext()) { 35 | this.nextIndex++; 36 | this.nodes[this.nextIndex] = node; 37 | results.push(this.nextIndex); 38 | } 39 | return results.join(","); 40 | }, 41 | 42 | findCssRelativeTo: function (reference, selector) { 43 | var elements = reference.querySelectorAll(selector); 44 | var results = []; 45 | for (var i = 0; i < elements.length; i++) { 46 | this.nextIndex++; 47 | this.nodes[this.nextIndex] = elements[i]; 48 | results.push(this.nextIndex); 49 | } 50 | return results.join(","); 51 | }, 52 | 53 | isAttached: function(index) { 54 | return this.nodes[index] && 55 | document.evaluate("ancestor-or-self::html", this.nodes[index], null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue != null; 56 | }, 57 | 58 | getNode: function(index) { 59 | if (CapybaraInvocation.allowUnattached || this.isAttached(index)) { 60 | return this.nodes[index]; 61 | } else { 62 | throw new Capybara.NodeNotAttachedError(index); 63 | } 64 | }, 65 | 66 | text: function (index) { 67 | var node = this.getNode(index); 68 | var type = (node.type || node.tagName).toLowerCase(); 69 | if (type == "textarea") { 70 | return node.innerHTML; 71 | } else { 72 | return node.innerText || node.textContent; 73 | } 74 | }, 75 | 76 | allText: function (index) { 77 | var node = this.getNode(index); 78 | return node.textContent; 79 | }, 80 | 81 | attribute: function (index, name) { 82 | var node = this.getNode(index); 83 | switch(name) { 84 | case 'checked': 85 | return node.checked; 86 | break; 87 | 88 | case 'disabled': 89 | return node.disabled; 90 | break; 91 | 92 | case 'multiple': 93 | return node.multiple; 94 | break; 95 | 96 | default: 97 | return node.getAttribute(name); 98 | } 99 | }, 100 | 101 | hasAttribute: function(index, name) { 102 | return this.getNode(index).hasAttribute(name); 103 | }, 104 | 105 | path: function(index) { 106 | return this.pathForNode(this.getNode(index)); 107 | }, 108 | 109 | pathForNode: function(node) { 110 | return "/" + this.getXPathNode(node).join("/"); 111 | }, 112 | 113 | getXPathNode: function(node, path) { 114 | path = path || []; 115 | if (node.parentNode) { 116 | path = this.getXPathNode(node.parentNode, path); 117 | } 118 | 119 | var first = node; 120 | while (first.previousSibling) 121 | first = first.previousSibling; 122 | 123 | var count = 0; 124 | var index = 0; 125 | var iter = first; 126 | while (iter) { 127 | if (iter.nodeType == 1 && iter.nodeName == node.nodeName) 128 | count++; 129 | if (iter.isSameNode(node)) 130 | index = count; 131 | iter = iter.nextSibling; 132 | continue; 133 | } 134 | 135 | if (node.nodeType == 1) 136 | path.push(node.nodeName.toLowerCase() + (node.id ? "[@id='"+node.id+"']" : count > 1 ? "["+index+"]" : '')); 137 | 138 | return path; 139 | }, 140 | 141 | tagName: function(index) { 142 | return this.getNode(index).tagName.toLowerCase(); 143 | }, 144 | 145 | submit: function(index) { 146 | return this.getNode(index).submit(); 147 | }, 148 | 149 | expectNodeAtPosition: function(node, pos) { 150 | var nodeAtPosition = 151 | document.elementFromPoint(pos.relativeX, pos.relativeY); 152 | var overlappingPath; 153 | 154 | if (nodeAtPosition) 155 | overlappingPath = this.pathForNode(nodeAtPosition) 156 | 157 | if (!this.isNodeOrChildAtPosition(node, pos, nodeAtPosition)) 158 | throw new Capybara.ClickFailed( 159 | this.pathForNode(node), 160 | overlappingPath, 161 | pos 162 | ); 163 | }, 164 | 165 | isNodeOrChildAtPosition: function(expectedNode, pos, currentNode) { 166 | if (currentNode == expectedNode) { 167 | return CapybaraInvocation.clickTest( 168 | expectedNode, 169 | pos.absoluteX, 170 | pos.absoluteY 171 | ); 172 | } else if (currentNode) { 173 | return this.isNodeOrChildAtPosition( 174 | expectedNode, 175 | pos, 176 | currentNode.parentNode 177 | ); 178 | } else { 179 | return false; 180 | } 181 | }, 182 | 183 | clickPosition: function(node) { 184 | var rects = node.getClientRects(); 185 | var rect; 186 | 187 | for (var i = 0; i < rects.length; i++) { 188 | rect = rects[i]; 189 | if (rect.width > 0 && rect.height > 0) 190 | return CapybaraInvocation.clickPosition(node, rect.left, rect.top, rect.width, rect.height); 191 | } 192 | 193 | var visible = this.isNodeVisible(node); 194 | throw new Capybara.UnpositionedElement(this.pathForNode(node), visible); 195 | }, 196 | 197 | click: function (index, action) { 198 | var node = this.getNode(index); 199 | node.scrollIntoViewIfNeeded(); 200 | var pos = this.clickPosition(node); 201 | CapybaraInvocation.hover(pos.relativeX, pos.relativeY); 202 | this.expectNodeAtPosition(node, pos); 203 | action(pos.absoluteX, pos.absoluteY); 204 | }, 205 | 206 | leftClick: function (index) { 207 | this.click(index, CapybaraInvocation.leftClick); 208 | }, 209 | 210 | doubleClick: function(index) { 211 | this.click(index, CapybaraInvocation.leftClick); 212 | this.click(index, CapybaraInvocation.doubleClick); 213 | }, 214 | 215 | rightClick: function(index) { 216 | this.click(index, CapybaraInvocation.rightClick); 217 | }, 218 | 219 | hover: function (index) { 220 | var node = this.getNode(index); 221 | node.scrollIntoViewIfNeeded(); 222 | 223 | var pos = this.clickPosition(node); 224 | CapybaraInvocation.hover(pos.absoluteX, pos.absoluteY); 225 | }, 226 | 227 | trigger: function (index, eventName) { 228 | var eventObject = document.createEvent("HTMLEvents"); 229 | eventObject.initEvent(eventName, true, true); 230 | this.getNode(index).dispatchEvent(eventObject); 231 | }, 232 | 233 | visible: function (index) { 234 | return this.isNodeVisible(this.getNode(index)); 235 | }, 236 | 237 | isNodeVisible: function(node) { 238 | while (node) { 239 | var style = node.ownerDocument.defaultView.getComputedStyle(node, null); 240 | if (style.getPropertyValue('display') == 'none' || style.getPropertyValue('visibility') == 'hidden') 241 | return false; 242 | 243 | node = node.parentElement; 244 | } 245 | return true; 246 | }, 247 | 248 | selected: function (index) { 249 | return this.getNode(index).selected; 250 | }, 251 | 252 | value: function(index) { 253 | return this.getNode(index).value; 254 | }, 255 | 256 | getInnerHTML: function(index) { 257 | return this.getNode(index).innerHTML; 258 | }, 259 | 260 | setInnerHTML: function(index, value) { 261 | this.getNode(index).innerHTML = value; 262 | return true; 263 | }, 264 | 265 | set: function (index, value) { 266 | var length, maxLength, node, strindex, textTypes, type; 267 | 268 | node = this.getNode(index); 269 | type = (node.type || node.tagName).toLowerCase(); 270 | textTypes = ["email", "number", "password", "search", "tel", "text", "textarea", "url"]; 271 | 272 | if (textTypes.indexOf(type) != -1) { 273 | this.focus(index); 274 | 275 | maxLength = this.attribute(index, "maxlength"); 276 | if (maxLength && value.length > maxLength) { 277 | length = maxLength; 278 | } else { 279 | length = value.length; 280 | } 281 | 282 | if (!node.readOnly) 283 | node.value = ""; 284 | 285 | for (strindex = 0; strindex < length; strindex++) { 286 | CapybaraInvocation.keypress(value[strindex]); 287 | } 288 | 289 | } else if (type === "checkbox" || type === "radio") { 290 | if (node.checked != (value === "true")) { 291 | this.leftClick(index); 292 | } 293 | 294 | } else if (type === "file") { 295 | this.attachedFiles = Array.prototype.slice.call(arguments, 1); 296 | this.leftClick(index); 297 | 298 | } else { 299 | node.value = value; 300 | } 301 | }, 302 | 303 | focus: function(index) { 304 | this.getNode(index).focus(); 305 | }, 306 | 307 | selectOption: function(index) { 308 | this.getNode(index).selected = true; 309 | this.trigger(index, "change"); 310 | }, 311 | 312 | unselectOption: function(index) { 313 | this.getNode(index).selected = false; 314 | this.trigger(index, "change"); 315 | }, 316 | 317 | centerPosition: function(element) { 318 | this.reflow(element); 319 | var rect = element.getBoundingClientRect(); 320 | var position = { 321 | x: rect.width / 2, 322 | y: rect.height / 2 323 | }; 324 | do { 325 | position.x += element.offsetLeft; 326 | position.y += element.offsetTop; 327 | } while ((element = element.offsetParent)); 328 | position.x = Math.floor(position.x); 329 | position.y = Math.floor(position.y); 330 | 331 | return position; 332 | }, 333 | 334 | reflow: function(element, force) { 335 | if (force || element.offsetWidth === 0) { 336 | var prop, oldStyle = {}, newStyle = {position: "absolute", visibility : "hidden", display: "block" }; 337 | for (prop in newStyle) { 338 | oldStyle[prop] = element.style[prop]; 339 | element.style[prop] = newStyle[prop]; 340 | } 341 | // force reflow 342 | element.offsetWidth; 343 | element.offsetHeight; 344 | for (prop in oldStyle) 345 | element.style[prop] = oldStyle[prop]; 346 | } 347 | }, 348 | 349 | dragTo: function (index, targetIndex) { 350 | var element = this.getNode(index), target = this.getNode(targetIndex); 351 | var position = this.centerPosition(element); 352 | var options = { 353 | clientX: position.x, 354 | clientY: position.y 355 | }; 356 | var mouseTrigger = function(eventName, options) { 357 | var eventObject = document.createEvent("MouseEvents"); 358 | eventObject.initMouseEvent(eventName, true, true, window, 0, 0, 0, options.clientX || 0, options.clientY || 0, false, false, false, false, 0, null); 359 | element.dispatchEvent(eventObject); 360 | }; 361 | mouseTrigger('mousedown', options); 362 | options.clientX += 1; 363 | options.clientY += 1; 364 | mouseTrigger('mousemove', options); 365 | 366 | position = this.centerPosition(target); 367 | options = { 368 | clientX: position.x, 369 | clientY: position.y 370 | }; 371 | mouseTrigger('mousemove', options); 372 | mouseTrigger('mouseup', options); 373 | }, 374 | 375 | equals: function(index, targetIndex) { 376 | return this.getNode(index) === this.getNode(targetIndex); 377 | } 378 | }; 379 | 380 | Capybara.ClickFailed = function(expectedPath, actualPath, position) { 381 | this.name = 'Capybara.ClickFailed'; 382 | this.message = 'Failed to click element ' + expectedPath; 383 | if (actualPath) 384 | this.message += ' because of overlapping element ' + actualPath; 385 | if (position) 386 | this.message += ' at position ' + position["absoluteX"] + ', ' + position["absoluteY"]; 387 | else 388 | this.message += ' at unknown position'; 389 | this.message += "; \nA screenshot of the page at the time of the failure has been written to " + CapybaraInvocation.render(); 390 | }; 391 | Capybara.ClickFailed.prototype = new Error(); 392 | Capybara.ClickFailed.prototype.constructor = Capybara.ClickFailed; 393 | 394 | Capybara.UnpositionedElement = function(path, visible) { 395 | this.name = 'Capybara.ClickFailed'; 396 | this.message = 'Failed to find position for element ' + path; 397 | if (!visible) 398 | this.message += ' because it is not visible'; 399 | }; 400 | Capybara.UnpositionedElement.prototype = new Error(); 401 | Capybara.UnpositionedElement.prototype.constructor = Capybara.UnpositionedElement; 402 | 403 | Capybara.NodeNotAttachedError = function(index) { 404 | this.name = 'Capybara.NodeNotAttachedError'; 405 | this.message = 'Element at ' + index + ' no longer present in the DOM'; 406 | }; 407 | Capybara.NodeNotAttachedError.prototype = new Error(); 408 | Capybara.NodeNotAttachedError.prototype.constructor = Capybara.NodeNotAttachedError; 409 | -------------------------------------------------------------------------------- /src/find_command.h: -------------------------------------------------------------------------------- 1 | #define CHECK_COMMAND(expectedName) \ 2 | if (strcmp(#expectedName, name) == 0) { \ 3 | return new expectedName(m_manager, arguments, this); \ 4 | } 5 | 6 | CHECK_COMMAND(Visit) 7 | CHECK_COMMAND(FindXpath) 8 | CHECK_COMMAND(Reset) 9 | CHECK_COMMAND(Node) 10 | CHECK_COMMAND(Evaluate) 11 | CHECK_COMMAND(Execute) 12 | CHECK_COMMAND(FrameFocus) 13 | CHECK_COMMAND(Header) 14 | CHECK_COMMAND(Render) 15 | CHECK_COMMAND(Body) 16 | CHECK_COMMAND(Status) 17 | CHECK_COMMAND(Headers) 18 | CHECK_COMMAND(SetCookie) 19 | CHECK_COMMAND(ClearCookies) 20 | CHECK_COMMAND(GetCookies) 21 | CHECK_COMMAND(SetProxy) 22 | CHECK_COMMAND(ConsoleMessages) 23 | CHECK_COMMAND(CurrentUrl) 24 | CHECK_COMMAND(ResizeWindow) 25 | CHECK_COMMAND(IgnoreSslErrors) 26 | CHECK_COMMAND(SetSkipImageLoading) 27 | CHECK_COMMAND(WindowFocus) 28 | CHECK_COMMAND(GetWindowHandles) 29 | CHECK_COMMAND(GetWindowHandle) 30 | CHECK_COMMAND(Authenticate) 31 | CHECK_COMMAND(EnableLogging) 32 | CHECK_COMMAND(SetConfirmAction) 33 | CHECK_COMMAND(SetPromptAction) 34 | CHECK_COMMAND(SetPromptText) 35 | CHECK_COMMAND(ClearPromptText) 36 | CHECK_COMMAND(JavascriptAlertMessages) 37 | CHECK_COMMAND(JavascriptConfirmMessages) 38 | CHECK_COMMAND(JavascriptPromptMessages) 39 | CHECK_COMMAND(GetTimeout) 40 | CHECK_COMMAND(SetTimeout) 41 | CHECK_COMMAND(SetUrlBlacklist) 42 | CHECK_COMMAND(Title) 43 | CHECK_COMMAND(Version) 44 | CHECK_COMMAND(FindCss) 45 | CHECK_COMMAND(Source) 46 | CHECK_COMMAND(SetHtml) 47 | CHECK_COMMAND(SetAttribute) 48 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Server.h" 2 | #include "IgnoreDebugOutput.h" 3 | #include 4 | #include 5 | #ifdef Q_OS_UNIX 6 | #include 7 | #endif 8 | 9 | int main(int argc, char **argv) { 10 | #ifdef Q_OS_UNIX 11 | if (setpgid(0, 0) < 0) { 12 | std::cerr << "Unable to set new process group." << std::endl; 13 | return 1; 14 | } 15 | #endif 16 | 17 | QApplication app(argc, argv); 18 | app.setApplicationName("capybara-webkit"); 19 | app.setOrganizationName("thoughtbot, inc"); 20 | app.setOrganizationDomain("thoughtbot.com"); 21 | 22 | ignoreDebugOutput(); 23 | Server server(0); 24 | 25 | if (server.start()) { 26 | std::cout << "Capybara-webkit server started, listening on port: " << server.server_port() << std::endl; 27 | return app.exec(); 28 | } else { 29 | std::cerr << "Couldn't start capybara-webkit server" << std::endl; 30 | return 1; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/pointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niklasb/webkit-server/c9e3a8394b8c51000c35f8a56fb770580562b544/src/pointer.png -------------------------------------------------------------------------------- /src/stable.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 32 | #include 33 | #else 34 | #include 35 | #endif 36 | #include 37 | #include 38 | #include 39 | #include 40 | -------------------------------------------------------------------------------- /src/webkit_server.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | TARGET = webkit_server 3 | DESTDIR = . 4 | PROJECT_DIR = $$_PRO_FILE_PWD_ 5 | BUILD_DIR = $${PROJECT_DIR}/build 6 | PRECOMPILED_DIR = $${BUILD_DIR} 7 | OBJECTS_DIR = $${BUILD_DIR} 8 | MOC_DIR = $${BUILD_DIR} 9 | HEADERS = \ 10 | Version.h \ 11 | EnableLogging.h \ 12 | Authenticate.h \ 13 | SetConfirmAction.h \ 14 | SetPromptAction.h \ 15 | SetPromptText.h \ 16 | ClearPromptText.h \ 17 | JavascriptAlertMessages.h \ 18 | JavascriptConfirmMessages.h \ 19 | JavascriptPromptMessages.h \ 20 | IgnoreSslErrors.h \ 21 | ResizeWindow.h \ 22 | CurrentUrl.h \ 23 | ConsoleMessages.h \ 24 | WebPage.h \ 25 | Server.h \ 26 | Connection.h \ 27 | Command.h \ 28 | SocketCommand.h \ 29 | Visit.h \ 30 | Reset.h \ 31 | Node.h \ 32 | JavascriptInvocation.h \ 33 | Evaluate.h \ 34 | Execute.h \ 35 | FrameFocus.h \ 36 | Response.h \ 37 | NetworkAccessManager.h \ 38 | NetworkCookieJar.h \ 39 | Header.h \ 40 | Render.h \ 41 | Body.h \ 42 | Status.h \ 43 | Headers.h \ 44 | UnsupportedContentHandler.h \ 45 | SetCookie.h \ 46 | ClearCookies.h \ 47 | GetCookies.h \ 48 | CommandParser.h \ 49 | CommandFactory.h \ 50 | SetProxy.h \ 51 | NullCommand.h \ 52 | PageLoadingCommand.h \ 53 | SetSkipImageLoading.h \ 54 | WebPageManager.h \ 55 | WindowFocus.h \ 56 | GetWindowHandles.h \ 57 | GetWindowHandle.h \ 58 | GetTimeout.h \ 59 | SetTimeout.h \ 60 | TimeoutCommand.h \ 61 | SetUrlBlacklist.h \ 62 | NoOpReply.h \ 63 | JsonSerializer.h \ 64 | InvocationResult.h \ 65 | ErrorMessage.h \ 66 | Title.h \ 67 | FindCss.h \ 68 | JavascriptCommand.h \ 69 | FindXpath.h \ 70 | NetworkReplyProxy.h \ 71 | IgnoreDebugOutput.h \ 72 | Source.h \ 73 | SetHtml.h \ 74 | SetAttribute.h 75 | 76 | SOURCES = \ 77 | Version.cpp \ 78 | EnableLogging.cpp \ 79 | Authenticate.cpp \ 80 | SetConfirmAction.cpp \ 81 | SetPromptAction.cpp \ 82 | SetPromptText.cpp \ 83 | ClearPromptText.cpp \ 84 | JavascriptAlertMessages.cpp \ 85 | JavascriptConfirmMessages.cpp \ 86 | JavascriptPromptMessages.cpp \ 87 | IgnoreSslErrors.cpp \ 88 | ResizeWindow.cpp \ 89 | CurrentUrl.cpp \ 90 | ConsoleMessages.cpp \ 91 | main.cpp \ 92 | WebPage.cpp \ 93 | Server.cpp \ 94 | Connection.cpp \ 95 | Command.cpp \ 96 | SocketCommand.cpp \ 97 | Visit.cpp \ 98 | Reset.cpp \ 99 | Node.cpp \ 100 | JavascriptInvocation.cpp \ 101 | Evaluate.cpp \ 102 | Execute.cpp \ 103 | FrameFocus.cpp \ 104 | Response.cpp \ 105 | NetworkAccessManager.cpp \ 106 | NetworkCookieJar.cpp \ 107 | Header.cpp \ 108 | Render.cpp \ 109 | body.cpp \ 110 | Status.cpp \ 111 | Headers.cpp \ 112 | UnsupportedContentHandler.cpp \ 113 | SetCookie.cpp \ 114 | ClearCookies.cpp \ 115 | GetCookies.cpp \ 116 | CommandParser.cpp \ 117 | CommandFactory.cpp \ 118 | SetProxy.cpp \ 119 | NullCommand.cpp \ 120 | PageLoadingCommand.cpp \ 121 | SetTimeout.cpp \ 122 | GetTimeout.cpp \ 123 | SetSkipImageLoading.cpp \ 124 | WebPageManager.cpp \ 125 | WindowFocus.cpp \ 126 | GetWindowHandles.cpp \ 127 | GetWindowHandle.cpp \ 128 | TimeoutCommand.cpp \ 129 | SetUrlBlacklist.cpp \ 130 | NoOpReply.cpp \ 131 | JsonSerializer.cpp \ 132 | InvocationResult.cpp \ 133 | ErrorMessage.cpp \ 134 | Title.cpp \ 135 | FindCss.cpp \ 136 | JavascriptCommand.cpp \ 137 | FindXpath.cpp \ 138 | NetworkReplyProxy.cpp \ 139 | IgnoreDebugOutput.cpp \ 140 | Source.cpp \ 141 | SetHtml.cpp \ 142 | SetAttribute.cpp 143 | 144 | RESOURCES = webkit_server.qrc 145 | QT += network 146 | greaterThan(QT_MAJOR_VERSION, 4) { 147 | QT += webkitwidgets 148 | } else { 149 | QT += webkit 150 | } 151 | lessThan(QT_MAJOR_VERSION, 5) { 152 | lessThan(QT_MINOR_VERSION, 8) { 153 | error(At least Qt 4.8.0 is required to run capybara-webkit.) 154 | } 155 | } 156 | CONFIG += console precompile_header 157 | CONFIG -= app_bundle 158 | PRECOMPILED_HEADER = stable.h 159 | 160 | -------------------------------------------------------------------------------- /src/webkit_server.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | capybara.js 4 | pointer.png 5 | 6 | 7 | -------------------------------------------------------------------------------- /webkit_server.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | CONFIG += ordered 3 | SUBDIRS += src/webkit_server.pro 4 | 5 | -------------------------------------------------------------------------------- /webkit_server.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python bindings for the `webkit-server `_ 3 | """ 4 | 5 | import sys, os 6 | import subprocess 7 | import re 8 | import socket 9 | import atexit 10 | import json 11 | 12 | # path to the `webkit_server` executable 13 | SERVER_EXEC = os.path.abspath(os.path.join(os.path.dirname(__file__), 14 | 'webkit_server')) 15 | 16 | 17 | class SelectionMixin(object): 18 | """ Implements a generic XPath selection for a class providing 19 | ``_get_xpath_ids``, ``_get_css_ids`` and ``get_node_factory`` methods. """ 20 | 21 | def xpath(self, xpath): 22 | """ Finds another node by XPath originating at the current node. """ 23 | return [self.get_node_factory().create(node_id) 24 | for node_id in self._get_xpath_ids(xpath).split(",") 25 | if node_id] 26 | 27 | def css(self, css): 28 | """ Finds another node by a CSS selector relative to the current node. """ 29 | return [self.get_node_factory().create(node_id) 30 | for node_id in self._get_css_ids(css).split(",") 31 | if node_id] 32 | 33 | 34 | class NodeFactory(object): 35 | """ Implements the default node factory. 36 | 37 | `client` is the associated client instance. """ 38 | 39 | def __init__(self, client): 40 | self.client = client 41 | 42 | def create(self, node_id): 43 | return Node(self.client, node_id) 44 | 45 | 46 | class NodeError(Exception): 47 | """ A problem occured within a ``Node`` instance method. """ 48 | pass 49 | 50 | 51 | class Node(SelectionMixin): 52 | """ Represents a DOM node in our Webkit session. 53 | 54 | `client` is the associated client instance. 55 | 56 | `node_id` is the internal ID that is used to identify the node when communicating 57 | with the server. """ 58 | 59 | def __init__(self, client, node_id): 60 | super(Node, self).__init__() 61 | self.client = client 62 | self.node_id = node_id 63 | 64 | def text(self): 65 | """ Returns the inner text (*not* HTML). """ 66 | return self._invoke("text") 67 | 68 | def get_bool_attr(self, name): 69 | """ Returns the value of a boolean HTML attribute like `checked` or `disabled` 70 | """ 71 | val = self.get_attr(name) 72 | return val is not None and val.lower() in ("true", name) 73 | 74 | def get_attr(self, name): 75 | """ Returns the value of an attribute. """ 76 | return self._invoke("attribute", name) 77 | 78 | def set_attr(self, name, value): 79 | """ Sets the value of an attribute. """ 80 | self.exec_script("node.setAttribute(%s, %s)" % (repr(name), repr(value))) 81 | 82 | def value(self): 83 | """ Returns the node's value. """ 84 | if self.is_multi_select(): 85 | return [opt.value() 86 | for opt in self.xpath(".//option") 87 | if opt["selected"]] 88 | else: 89 | return self._invoke("value") 90 | 91 | def set(self, value): 92 | """ Sets the node content to the given value (e.g. for input fields). """ 93 | self._invoke("set", value) 94 | 95 | def path(self): 96 | """ Returns an XPath expression that uniquely identifies the current node. """ 97 | return self._invoke("path") 98 | 99 | def submit(self): 100 | """ Submits a form node, then waits for the page to completely load. """ 101 | self.eval_script("node.submit()") 102 | 103 | def eval_script(self, js): 104 | """ Evaluate arbitrary Javascript with the ``node`` variable bound to the 105 | current node. """ 106 | return self.client.eval_script(self._build_script(js)) 107 | 108 | def exec_script(self, js): 109 | """ Execute arbitrary Javascript with the ``node`` variable bound to 110 | the current node. """ 111 | self.client.exec_script(self._build_script(js)) 112 | 113 | def _build_script(self, js): 114 | return "var node = Capybara.nodes[%s]; %s;" % (self.node_id, js) 115 | 116 | def select_option(self): 117 | """ Selects an option node. """ 118 | self._invoke("selectOption") 119 | 120 | def unselect_options(self): 121 | """ Unselects an option node (only possible within a multi-select). """ 122 | if self.xpath("ancestor::select")[0].is_multi_select(): 123 | self._invoke("unselectOption") 124 | else: 125 | raise NodeError("Unselect not allowed.") 126 | 127 | def click(self): 128 | """ Alias for ``left_click``. """ 129 | self.left_click() 130 | 131 | def left_click(self): 132 | """ Left clicks the current node, then waits for the page 133 | to fully load. """ 134 | self._invoke("leftClick") 135 | 136 | def right_click(self): 137 | """ Right clicks the current node, then waits for the page 138 | to fully load. """ 139 | self._invoke("rightClick") 140 | 141 | def double_click(self): 142 | """ Double clicks the current node, then waits for the page 143 | to fully load. """ 144 | self._invoke("doubleClick") 145 | 146 | def hover(self): 147 | """ Hovers over the current node, then waits for the page 148 | to fully load. """ 149 | self._invoke("hover") 150 | 151 | def focus(self): 152 | """ Puts the focus onto the current node, then waits for the page 153 | to fully load. """ 154 | self._invoke("focus") 155 | 156 | def drag_to(self, element): 157 | """ Drag the node to another one. """ 158 | self._invoke("dragTo", element.node_id) 159 | 160 | def tag_name(self): 161 | """ Returns the tag name of the current node. """ 162 | return self._invoke("tagName") 163 | 164 | def is_visible(self): 165 | """ Checks whether the current node is visible. """ 166 | return self._invoke("visible") == "true" 167 | 168 | def is_attached(self): 169 | """ Checks whether the current node is actually existing on the currently 170 | active web page. """ 171 | return self._invoke("isAttached") == "true" 172 | 173 | def is_selected(self): 174 | """ is the ``selected`` attribute set for this node? """ 175 | return self.get_bool_attr("selected") 176 | 177 | def is_checked(self): 178 | """ is the ``checked`` attribute set for this node? """ 179 | return self.get_bool_attr("checked") 180 | 181 | def is_disabled(self): 182 | """ is the ``disabled`` attribute set for this node? """ 183 | return self.get_bool_attr("disabled") 184 | 185 | def is_multi_select(self): 186 | """ is this node a multi-select? """ 187 | return self.tag_name() == "select" and self.get_bool_attr("multiple") 188 | 189 | def _get_xpath_ids(self, xpath): 190 | """ Implements a mechanism to get a list of node IDs for an relative XPath 191 | query. """ 192 | return self._invoke("findXpathWithin", xpath) 193 | 194 | def _get_css_ids(self, css): 195 | """ Implements a mechanism to get a list of node IDs for an relative CSS 196 | query. """ 197 | return self._invoke("findCssWithin", css) 198 | 199 | def get_node_factory(self): 200 | """ Returns the associated node factory. """ 201 | return self.client.get_node_factory() 202 | 203 | def __repr__(self): 204 | return "" % self.path() 205 | 206 | def _invoke(self, cmd, *args): 207 | return self.client.issue_node_cmd(cmd, "false", self.node_id, *args) 208 | 209 | 210 | def _normalize_header(key): 211 | return "-".join(part[0].upper() + part[1:].lower() for part in key.split("-")) 212 | 213 | 214 | class Client(SelectionMixin): 215 | """ Wrappers for the webkit_server commands. 216 | 217 | If `connection` is not specified, a new instance of ``ServerConnection`` is 218 | created. 219 | 220 | `node_factory_class` can be set to a value different from the default, in which 221 | case a new instance of the given class will be used to create nodes. The given 222 | class must accept a client instance through its constructor and support a 223 | ``create`` method that takes a node ID as an argument and returns a node object. 224 | """ 225 | 226 | def __init__(self, 227 | connection = None, 228 | node_factory_class = NodeFactory): 229 | super(Client, self).__init__() 230 | self.conn = connection or ServerConnection() 231 | self._node_factory = node_factory_class(self) 232 | 233 | def visit(self, url): 234 | """ Goes to a given URL. """ 235 | self.conn.issue_command("Visit", url) 236 | 237 | def body(self): 238 | """ Returns the current DOM as HTML. """ 239 | return self.conn.issue_command("Body") 240 | 241 | def source(self): 242 | """ Returns the source of the page as it was originally 243 | served by the web server. """ 244 | return self.conn.issue_command("Source") 245 | 246 | def url(self): 247 | """ Returns the current location. """ 248 | return self.conn.issue_command("CurrentUrl") 249 | 250 | def set_header(self, key, value): 251 | """ Sets a HTTP header for future requests. """ 252 | self.conn.issue_command("Header", _normalize_header(key), value) 253 | 254 | def reset(self): 255 | """ Resets the current web session. """ 256 | self.conn.issue_command("Reset") 257 | 258 | def status_code(self): 259 | """ Returns the numeric HTTP status of the last response. """ 260 | return int(self.conn.issue_command("Status")) 261 | 262 | def headers(self): 263 | """ Returns a list of the last HTTP response headers. 264 | Header keys are normalized to capitalized form, as in `User-Agent`. 265 | """ 266 | headers = self.conn.issue_command("Headers") 267 | res = [] 268 | for header in headers.split("\r"): 269 | key, value = header.split(": ", 1) 270 | for line in value.split("\n"): 271 | res.append((_normalize_header(key), line)) 272 | return res 273 | 274 | def eval_script(self, expr): 275 | """ Evaluates a piece of Javascript in the context of the current page and 276 | returns its value. """ 277 | ret = self.conn.issue_command("Evaluate", expr) 278 | return json.loads("[%s]" % ret)[0] 279 | 280 | def exec_script(self, script): 281 | """ Executes a piece of Javascript in the context of the current page. """ 282 | self.conn.issue_command("Execute", script) 283 | 284 | def render(self, path, width = 1024, height = 1024): 285 | """ Renders the current page to a PNG file (viewport size in pixels). """ 286 | self.conn.issue_command("Render", path, width, height) 287 | 288 | def set_viewport_size(self, width, height): 289 | """ Sets the viewport size. """ 290 | self.conn.issue_command("ResizeWindow", width, height) 291 | 292 | def set_cookie(self, cookie): 293 | """ Sets a cookie for future requests (must be in correct cookie string 294 | format). """ 295 | self.conn.issue_command("SetCookie", cookie) 296 | 297 | def clear_cookies(self): 298 | """ Deletes all cookies. """ 299 | self.conn.issue_command("ClearCookies") 300 | 301 | def cookies(self): 302 | """ Returns a list of all cookies in cookie string format. """ 303 | return [line.strip() 304 | for line in self.conn.issue_command("GetCookies").split("\n") 305 | if line.strip()] 306 | 307 | def set_error_tolerant(self, tolerant=True): 308 | """ DEPRECATED! This function is a no-op now. 309 | 310 | Used to set or unset the error tolerance flag in the server. If this flag 311 | as set, dropped requests or erroneous responses would not lead to an error. """ 312 | return 313 | 314 | def set_attribute(self, attr, value = True): 315 | """ Sets a custom attribute for our Webkit instance. Possible attributes are: 316 | 317 | * ``auto_load_images`` 318 | * ``dns_prefetch_enabled`` 319 | * ``plugins_enabled`` 320 | * ``private_browsing_enabled`` 321 | * ``javascript_can_open_windows`` 322 | * ``javascript_can_access_clipboard`` 323 | * ``offline_storage_database_enabled`` 324 | * ``offline_web_application_cache_enabled`` 325 | * ``local_storage_enabled`` 326 | * ``local_storage_database_enabled`` 327 | * ``local_content_can_access_remote_urls`` 328 | * ``local_content_can_access_file_urls`` 329 | * ``accelerated_compositing_enabled`` 330 | * ``site_specific_quirks_enabled`` 331 | 332 | For all those options, ``value`` must be a boolean. You can find more 333 | information about these options `in the QT docs 334 | `_. 335 | """ 336 | value = "true" if value else "false" 337 | self.conn.issue_command("SetAttribute", 338 | self._normalize_attr(attr), 339 | value) 340 | 341 | def reset_attribute(self, attr): 342 | """ Resets a custom attribute. """ 343 | self.conn.issue_command("SetAttribute", 344 | self._normalize_attr(attr), 345 | "reset") 346 | 347 | def set_html(self, html, url = None): 348 | """ Sets custom HTML in our Webkit session and allows to specify a fake URL. 349 | Scripts and CSS is dynamically fetched as if the HTML had been loaded from 350 | the given URL. """ 351 | if url: 352 | self.conn.issue_command('SetHtml', html, url) 353 | else: 354 | self.conn.issue_command('SetHtml', html) 355 | 356 | def set_proxy(self, host = "localhost", 357 | port = 0, 358 | user = "", 359 | password = ""): 360 | """ Sets a custom HTTP proxy to use for future requests. """ 361 | self.conn.issue_command("SetProxy", host, port, user, password) 362 | 363 | def set_timeout(self, timeout): 364 | """ Set timeout for every webkit-server command """ 365 | self.conn.issue_command("SetTimeout", timeout) 366 | 367 | def get_timeout(self): 368 | """ Return timeout for every webkit-server command """ 369 | return int(self.conn.issue_command("GetTimeout")) 370 | 371 | def clear_proxy(self): 372 | """ Resets custom HTTP proxy (use none in future requests). """ 373 | self.conn.issue_command("ClearProxy") 374 | 375 | def issue_node_cmd(self, *args): 376 | """ Issues a node-specific command. """ 377 | return self.conn.issue_command("Node", *args) 378 | 379 | def get_node_factory(self): 380 | """ Returns the associated node factory. """ 381 | return self._node_factory 382 | 383 | def _get_xpath_ids(self, xpath): 384 | """ Implements a mechanism to get a list of node IDs for an absolute XPath 385 | query. """ 386 | return self.conn.issue_command("FindXpath", xpath) 387 | 388 | def _get_css_ids(self, css): 389 | """ Implements a mechanism to get a list of node IDs for an absolute CSS query 390 | query. """ 391 | return self.conn.issue_command("FindCss", css) 392 | 393 | def _normalize_attr(self, attr): 394 | """ Transforms a name like ``auto_load_images`` into ``AutoLoadImages`` 395 | (allows Webkit option names to blend in with Python naming). """ 396 | return ''.join(x.capitalize() for x in attr.split("_")) 397 | 398 | 399 | class WebkitServerError(Exception): 400 | """ Raised when the Webkit server experiences an error. """ 401 | 402 | 403 | class NoX11Error(WebkitServerError): 404 | """ Raised when the Webkit server cannot connect to X. """ 405 | 406 | 407 | class Server(object): 408 | """ Manages a Webkit server process. If `binary` is given, the specified 409 | ``webkit_server`` binary is used instead of the included one. """ 410 | 411 | def __init__(self, binary = None): 412 | binary = binary or SERVER_EXEC 413 | self._server = subprocess.Popen([binary], 414 | stdin = subprocess.PIPE, 415 | stdout = subprocess.PIPE, 416 | stderr = subprocess.PIPE) 417 | output = self._server.stdout.readline() 418 | 419 | try: 420 | self._port = int(re.search(b"port: (\d+)", output).group(1)) 421 | except AttributeError: 422 | err = self._server.stderr.read().decode("utf-8") 423 | if "Could not connect to display" in err: 424 | raise NoX11Error("Could not connect to X server. " 425 | "Try calling dryscrape.start_xvfb() before creating a session.") 426 | else: 427 | raise WebkitServerError("webkit-server failed to start. Output:\n" + err) 428 | 429 | # on program termination, kill the server instance 430 | atexit.register(self.kill) 431 | 432 | def kill(self): 433 | """ Kill the process. """ 434 | self._server.kill() 435 | self._server.communicate() 436 | 437 | def connect(self): 438 | """ Returns a new socket connection to this server. """ 439 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 440 | sock.connect(("127.0.0.1", self._port)) 441 | return sock 442 | 443 | 444 | _default_server = None 445 | def get_default_server(): 446 | """ Returns a singleton Server instance (possibly after creating it, if it 447 | doesn't exist yet). """ 448 | global _default_server 449 | if not _default_server: 450 | _default_server = Server() 451 | return _default_server 452 | 453 | 454 | class NoResponseError(Exception): 455 | """ Raised when the Webkit server does not respond. """ 456 | 457 | 458 | class InvalidResponseError(Exception): 459 | """ Raised when the Webkit server signaled an error. """ 460 | 461 | 462 | class EndOfStreamError(Exception): 463 | """ Raised when the Webkit server closed the connection unexpectedly. """ 464 | def __init__(self, msg="Unexpected end of file"): 465 | super(Exception, self).__init__(msg) 466 | 467 | 468 | class SocketBuffer(object): 469 | """ A convenience class for buffered reads from a socket. """ 470 | def __init__(self, f): 471 | """ `f` is expected to be an open socket. """ 472 | self.f = f 473 | self.buf = b'' 474 | 475 | def read_line(self): 476 | """ Consume one line from the stream. """ 477 | while True: 478 | newline_idx = self.buf.find(b"\n") 479 | if newline_idx >= 0: 480 | res = self.buf[:newline_idx] 481 | self.buf = self.buf[newline_idx + 1:] 482 | return res 483 | chunk = self.f.recv(4096) 484 | if not chunk: 485 | raise EndOfStreamError() 486 | self.buf += chunk 487 | 488 | def read(self, n): 489 | """ Consume `n` characters from the stream. """ 490 | while len(self.buf) < n: 491 | chunk = self.f.recv(4096) 492 | if not chunk: 493 | raise EndOfStreamError() 494 | self.buf += chunk 495 | res, self.buf = self.buf[:n], self.buf[n:] 496 | return res 497 | 498 | 499 | class ServerConnection(object): 500 | """ A connection to a Webkit server. 501 | 502 | `server` is a server instance or `None` if a singleton server should be connected 503 | to (will be started if necessary). """ 504 | 505 | def __init__(self, server = None): 506 | super(ServerConnection, self).__init__() 507 | self._sock = (server or get_default_server()).connect() 508 | self.buf = SocketBuffer(self._sock) 509 | self.issue_command("IgnoreSslErrors") 510 | 511 | def issue_command(self, cmd, *args): 512 | """ Sends and receives a message to/from the server """ 513 | self._writeline(cmd) 514 | self._writeline(str(len(args))) 515 | for arg in args: 516 | arg = str(arg) 517 | self._writeline(str(len(arg))) 518 | self._sock.sendall(arg.encode("utf-8")) 519 | 520 | return self._read_response() 521 | 522 | def _read_response(self): 523 | """ Reads a complete response packet from the server """ 524 | result = self.buf.read_line().decode("utf-8") 525 | if not result: 526 | raise NoResponseError("No response received from server.") 527 | 528 | msg = self._read_message() 529 | if result != "ok": 530 | raise InvalidResponseError(msg) 531 | 532 | return msg 533 | 534 | def _read_message(self): 535 | """ Reads a single size-annotated message from the server """ 536 | size = int(self.buf.read_line().decode("utf-8")) 537 | return self.buf.read(size).decode("utf-8") 538 | 539 | def _writeline(self, line): 540 | """ Writes a line to the underlying socket. """ 541 | self._sock.sendall(line.encode("utf-8") + b"\n") 542 | --------------------------------------------------------------------------------