├── .gitignore ├── .rspec ├── .travis.yml ├── Appraisals ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── NEWS.md ├── README.md ├── Rakefile ├── Vagrantfile ├── bin └── Info.plist ├── capybara-webkit.gemspec ├── extconf.rb ├── gemfiles ├── 2.15.gemfile └── master.gemfile ├── lib ├── capybara-webkit.rb ├── capybara │ ├── webkit.rb │ └── webkit │ │ ├── browser.rb │ │ ├── configuration.rb │ │ ├── connection.rb │ │ ├── cookie_jar.rb │ │ ├── driver.rb │ │ ├── errors.rb │ │ ├── matchers.rb │ │ ├── node.rb │ │ ├── server.rb │ │ └── version.rb └── capybara_webkit_builder.rb ├── spec ├── browser_spec.rb ├── capybara_webkit_builder_spec.rb ├── configuration_spec.rb ├── connection_spec.rb ├── cookie_jar_spec.rb ├── driver_rendering_spec.rb ├── driver_resize_window_spec.rb ├── driver_spec.rb ├── errors_spec.rb ├── fixtures │ ├── exit_text.rb │ └── fake_server.sh ├── integration │ └── session_spec.rb ├── node_spec.rb ├── selenium_compatibility_spec.rb ├── self_signed_ssl_cert.rb ├── server_spec.rb ├── spec_helper.rb └── support │ ├── app_runner.rb │ ├── matchers │ └── include_response.rb │ └── output_writer.rb ├── src ├── AcceptAlert.cpp ├── AcceptAlert.h ├── AllowUrl.cpp ├── AllowUrl.h ├── Authenticate.cpp ├── Authenticate.h ├── BlacklistedRequestHandler.cpp ├── BlacklistedRequestHandler.h ├── BlockUrl.cpp ├── BlockUrl.h ├── 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 ├── CustomHeadersRequestHandler.cpp ├── CustomHeadersRequestHandler.h ├── EnableLogging.cpp ├── EnableLogging.h ├── ErrorMessage.cpp ├── ErrorMessage.h ├── Evaluate.cpp ├── Evaluate.h ├── EvaluateAsync.cpp ├── EvaluateAsync.h ├── Execute.cpp ├── Execute.h ├── FindCss.cpp ├── FindCss.h ├── FindModal.cpp ├── FindModal.h ├── FindXpath.cpp ├── FindXpath.h ├── FrameFocus.cpp ├── FrameFocus.h ├── FrameTitle.cpp ├── FrameTitle.h ├── FrameUrl.cpp ├── FrameUrl.h ├── GetCookies.cpp ├── GetCookies.h ├── GetTimeout.cpp ├── GetTimeout.h ├── GetWindowHandle.cpp ├── GetWindowHandle.h ├── GetWindowHandles.cpp ├── GetWindowHandles.h ├── GoBack.cpp ├── GoBack.h ├── GoForward.cpp ├── GoForward.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 ├── MissingContentHeaderRequestHandler.cpp ├── MissingContentHeaderRequestHandler.h ├── NetworkAccessManager.cpp ├── NetworkAccessManager.h ├── NetworkCookieJar.cpp ├── NetworkCookieJar.h ├── NetworkReplyProxy.cpp ├── NetworkReplyProxy.h ├── NetworkRequestFactory.cpp ├── NetworkRequestFactory.h ├── NoOpReply.cpp ├── NoOpReply.h ├── Node.cpp ├── Node.h ├── NullCommand.cpp ├── NullCommand.h ├── PageLoadingCommand.cpp ├── PageLoadingCommand.h ├── Refresh.cpp ├── Refresh.h ├── Render.cpp ├── Render.h ├── RequestHandler.cpp ├── RequestHandler.h ├── Reset.cpp ├── Reset.h ├── Response.cpp ├── Response.h ├── Server.cpp ├── Server.h ├── SetConfirmAction.cpp ├── SetConfirmAction.h ├── SetCookie.cpp ├── SetCookie.h ├── SetPromptAction.cpp ├── SetPromptAction.h ├── SetPromptText.cpp ├── SetPromptText.h ├── SetProxy.cpp ├── SetProxy.h ├── SetSkipImageLoading.cpp ├── SetSkipImageLoading.h ├── SetTimeout.cpp ├── SetTimeout.h ├── SetUnknownUrlMode.cpp ├── SetUnknownUrlMode.h ├── SetUrlBlacklist.cpp ├── SetUrlBlacklist.h ├── SocketCommand.cpp ├── SocketCommand.h ├── Status.cpp ├── Status.h ├── StdinNotifier.cpp ├── StdinNotifier.h ├── TimeoutCommand.cpp ├── TimeoutCommand.h ├── Title.cpp ├── Title.h ├── UnknownUrlHandler.cpp ├── UnknownUrlHandler.h ├── UnsupportedContentHandler.cpp ├── UnsupportedContentHandler.h ├── Version.cpp ├── Version.h ├── Visit.cpp ├── Visit.h ├── WebPage.cpp ├── WebPage.h ├── WebPageManager.cpp ├── WebPageManager.h ├── WindowClose.cpp ├── WindowClose.h ├── WindowCommand.cpp ├── WindowCommand.h ├── WindowFocus.cpp ├── WindowFocus.h ├── WindowMaximize.cpp ├── WindowMaximize.h ├── WindowOpen.cpp ├── WindowOpen.h ├── WindowResize.cpp ├── WindowResize.h ├── WindowSize.cpp ├── WindowSize.h ├── body.cpp ├── capybara.js ├── find_command.h ├── main.cpp ├── pointer.png ├── stable.h ├── webkit_server.pro └── webkit_server.qrc ├── templates ├── Command.cpp └── Command.h ├── test ├── testignoredebugoutput.cpp └── testwebkitserver.pro ├── vagrant_setup.sh └── webkit_server.pro /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | bin/webkit_server* 3 | test/testwebkitserver 4 | test/moc_predefs.h 5 | test/target_wrapper.sh 6 | *.swo 7 | *~ 8 | *.o 9 | *.moc 10 | Makefile* 11 | qrc_* 12 | *.xcodeproj 13 | *.app 14 | moc_*.cpp 15 | .bundle 16 | pkg 17 | src/webkit_server 18 | src/webkit_server.exe 19 | .DS_Store 20 | tmp 21 | .rvmrc 22 | src/debug 23 | webkit_server.pro.user 24 | .vagrant 25 | *.gch 26 | .ruby-version 27 | .ruby-gemset 28 | .idea 29 | .qmake.stash 30 | gemfiles/*.lock 31 | /src/build/ 32 | save_path_tmp/ -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: ruby 4 | rvm: 5 | - 2.2 6 | - 2.4 7 | - 2.5 8 | - jruby-9.1.16.0 9 | notifications: 10 | email: false 11 | script: xvfb-run bundle exec rake 12 | env: 13 | global: 14 | - BUNDLE_JOBS=4 15 | - NOKOGIRI_USE_SYSTEM_LIBRARIES=true 16 | - QMAKE=/usr/lib/x86_64-linux-gnu/qt5/bin/qmake 17 | - JAVA_OPTS=-Djava.security.egd=file:/dev/urandom 18 | addons: 19 | apt: 20 | sources: 21 | - ubuntu-sdk-team 22 | packages: 23 | - libqt5webkit5-dev 24 | - qtdeclarative5-dev 25 | matrix: 26 | include: 27 | - rvm: 1.9.3 28 | gemfile: gemfiles/2.15.gemfile 29 | env: QMAKE=/usr/lib/x86_64-linux-gnu/qt4/bin/qmake 30 | - rvm: jruby-19mode 31 | gemfile: gemfiles/2.15.gemfile 32 | - rvm: 2.3 33 | gemfile: gemfiles/2.15.gemfile 34 | - rvm: 2.5 35 | gemfile: gemfiles/master.gemfile 36 | allow_failures: 37 | - gemfile: gemfiles/master.gemfile 38 | gemfile: 39 | - Gemfile 40 | before_install: 41 | - gem install bundler 42 | install: bundle 43 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise "2.15" do 2 | gem "capybara", "~> 2.15.0" 3 | gem 'addressable', '< 2.5.0', :platforms=>[:ruby_19, :jruby_19] # 2.5 requires public_suffix which requires ruby 2.0 4 | gem 'nokogiri', '< 1.7.0', :platforms=>[:ruby_19, :jruby_19] # 1.7.0 requires ruby 2.1+ 5 | end 6 | 7 | appraise "master" do 8 | gem "capybara", github: "jnicklas/capybara" 9 | gem "puma" 10 | end 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | We love pull requests from everyone. We expect users to follow our 2 | [code of conduct] while submitting code or comments. 3 | 4 | [code of conduct]: https://thoughtbot.com/open-source-code-of-conduct 5 | 6 | ## Dependencies 7 | 8 | Some of the tests depend on the `identify` command that comes with Imagemagick. 9 | Imagemagick can be installed via [homebrew](http://mxcl.github.com/homebrew/) on 10 | Mac OS X: 11 | 12 | brew install imagemagick 13 | 14 | If you prefer, you can use a [Vagrant](http://www.vagrantup.com/) virtual 15 | machine. The Vagrantfile in the capybara-webkit repository will get you up and 16 | running with all the development dependencies: 17 | 18 | gem install vagrant 19 | vagrant up 20 | vagrant ssh 21 | cd /vagrant 22 | rake 23 | 24 | ## Contributing 25 | 26 | 1. Fork the repo. 27 | 28 | 2. Run the tests. We only take pull requests with passing tests, and it's great 29 | to know that you have a clean slate: `bundle && bundle exec rake` 30 | 31 | 3. Add a test for your change. Only refactoring and documentation changes 32 | require no new tests. If you are adding functionality or fixing a bug, we need 33 | a test! 34 | 35 | 4. Make the test pass. 36 | 37 | 5. Push to your fork and submit a pull request. 38 | 39 | 40 | At this point you're waiting on us. We like to at least comment on, if not 41 | accept, pull requests within three business days (and, typically, one business 42 | day). We may suggest some changes or improvements or alternatives. 43 | 44 | Some things that will increase the chance that your pull request is accepted, 45 | taken straight from the Ruby on Rails guide: 46 | 47 | * Use Rails idioms and helpers 48 | * Include tests that fail without your code, and pass with it 49 | * Update the documentation, the surrounding one, examples elsewhere, guides, 50 | whatever is affected by your contribution 51 | 52 | Syntax: 53 | 54 | * Two spaces, no tabs. 55 | * No trailing whitespace. Blank lines should not have any space. 56 | * Prefer &&/|| over and/or. 57 | * MyClass.my_method(my_arg) not my_method( my_arg ) or my_method my_arg. 58 | * a = b and not a=b. 59 | * Follow the conventions you see used in the source already. 60 | 61 | And in case we didn't emphasize it enough: we love tests! 62 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | gem "puma" -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | capybara-webkit (1.15.0) 5 | capybara (>= 2.3, < 4.0) 6 | json 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | addressable (2.5.2) 12 | public_suffix (>= 2.0.2, < 4.0) 13 | appraisal (2.2.0) 14 | bundler 15 | rake 16 | thor (>= 0.14.0) 17 | capybara (3.0.1) 18 | addressable 19 | mini_mime (>= 0.1.3) 20 | nokogiri (~> 1.8) 21 | rack (>= 1.6.0) 22 | rack-test (>= 0.6.3) 23 | xpath (~> 3.0) 24 | diff-lcs (1.3) 25 | ffi (1.9.18) 26 | ffi (1.9.18-java) 27 | json (2.1.0) 28 | json (2.1.0-java) 29 | launchy (2.4.3) 30 | addressable (~> 2.3) 31 | launchy (2.4.3-java) 32 | addressable (~> 2.3) 33 | spoon (~> 0.0.1) 34 | mini_magick (4.8.0) 35 | mini_mime (1.0.0) 36 | mini_portile2 (2.3.0) 37 | mustermann (1.0.1) 38 | nokogiri (1.8.2) 39 | mini_portile2 (~> 2.3.0) 40 | nokogiri (1.8.2-java) 41 | nokogiri (1.8.2-x86-mingw32) 42 | mini_portile2 (~> 2.3.0) 43 | public_suffix (3.0.0) 44 | puma (3.11.3) 45 | puma (3.11.3-java) 46 | rack (2.0.3) 47 | rack-protection (2.0.0) 48 | rack 49 | rack-test (1.0.0) 50 | rack (>= 1.0, < 3) 51 | rake (11.3.0) 52 | rspec (3.7.0) 53 | rspec-core (~> 3.7.0) 54 | rspec-expectations (~> 3.7.0) 55 | rspec-mocks (~> 3.7.0) 56 | rspec-core (3.7.0) 57 | rspec-support (~> 3.7.0) 58 | rspec-expectations (3.7.0) 59 | diff-lcs (>= 1.2.0, < 2.0) 60 | rspec-support (~> 3.7.0) 61 | rspec-mocks (3.7.0) 62 | diff-lcs (>= 1.2.0, < 2.0) 63 | rspec-support (~> 3.7.0) 64 | rspec-support (3.7.0) 65 | sinatra (2.0.0) 66 | mustermann (~> 1.0) 67 | rack (~> 2.0) 68 | rack-protection (= 2.0.0) 69 | tilt (~> 2.0) 70 | spoon (0.0.6) 71 | ffi 72 | thor (0.20.0) 73 | tilt (2.0.8) 74 | xpath (3.0.0) 75 | nokogiri (~> 1.8) 76 | 77 | PLATFORMS 78 | java 79 | ruby 80 | x86-mingw32 81 | 82 | DEPENDENCIES 83 | appraisal 84 | capybara-webkit! 85 | launchy 86 | mini_magick 87 | puma 88 | rake (< 12.0.0) 89 | rspec (~> 3.5) 90 | sinatra 91 | 92 | BUNDLED WITH 93 | 1.16.1 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2015 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 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | require 'rspec/core/rake_task' 3 | require_relative './lib/capybara_webkit_builder' 4 | require 'appraisal' 5 | 6 | namespace :bundler do 7 | Bundler::GemHelper.install_tasks 8 | end 9 | 10 | desc "Generate a Makefile using qmake" 11 | file 'Makefile' do 12 | CapybaraWebkitBuilder.makefile('CONFIG+=test') or exit(1) 13 | end 14 | 15 | desc "Regenerate dependencies using qmake" 16 | task :qmake => 'Makefile' do 17 | CapybaraWebkitBuilder.qmake or exit(1) 18 | end 19 | 20 | desc "Build the webkit server" 21 | task :build => :qmake do 22 | CapybaraWebkitBuilder.build or exit(1) 23 | end 24 | 25 | desc "Run QtTest unit tests for webkit server" 26 | task :check => :build do 27 | sh("make check") or exit(1) 28 | end 29 | 30 | file 'bin/webkit_server' => :build 31 | 32 | RSpec::Core::RakeTask.new do |t| 33 | t.pattern = "spec/**/*_spec.rb" 34 | t.rspec_opts = "--format progress" 35 | end 36 | 37 | task :spec => :build 38 | 39 | desc "Default: build and run all specs" 40 | task :default => [:check, :spec] 41 | 42 | desc "Generate a new command called NAME" 43 | task :generate_command do 44 | name = ENV['NAME'] or raise "Provide a name with NAME=" 45 | 46 | %w(h cpp).each do |extension| 47 | File.open("templates/Command.#{extension}", "r") do |source_file| 48 | contents = source_file.read 49 | contents.gsub!("NAME", name) 50 | File.open("src/#{name}.#{extension}", "w") do |target_file| 51 | target_file.write(contents) 52 | end 53 | end 54 | end 55 | 56 | Dir.glob("src/*.pro").each do |project_file_name| 57 | project = IO.read(project_file_name) 58 | project.gsub!(/^(HEADERS = .*)/, "\\1\n #{name}.h \\") 59 | project.gsub!(/^(SOURCES = .*)/, "\\1\n #{name}.cpp \\") 60 | File.open(project_file_name, "w") { |file| file.write(project) } 61 | end 62 | 63 | File.open("src/find_command.h", "a") do |file| 64 | file.write("CHECK_COMMAND(#{name})\n") 65 | end 66 | 67 | command_factory_file_name = "src/CommandFactory.cpp" 68 | command_factory = IO.read(command_factory_file_name) 69 | command_factory.sub!(/^$/, "#include \"#{name}.h\"\n") 70 | File.open(command_factory_file_name, "w") { |file| file.write(command_factory) } 71 | end 72 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant::Config.run do |config| 5 | config.vm.box = "precise32" 6 | config.vm.provision :shell, :path => 'vagrant_setup.sh' 7 | end 8 | -------------------------------------------------------------------------------- /bin/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIconFile 6 | 7 | CFBundlePackageType 8 | APPL 9 | CFBundleGetInfoString 10 | Created by Qt/QMake 11 | CFBundleSignature 12 | ???? 13 | CFBundleExecutable 14 | webkit_server 15 | CFBundleIdentifier 16 | com.yourcompany.webkit_server 17 | NOTE 18 | This file was generated by Qt/QMake. 19 | LSBackgroundOnly 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /capybara-webkit.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | require "capybara/webkit/version" 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "capybara-webkit" 6 | s.version = Capybara::Driver::Webkit::VERSION.dup 7 | s.authors = ["thoughtbot", "Joe Ferris", "Matt Horan", "Matt Mongeau", 8 | "Mike Burns", "Jason Morrison"] 9 | s.email = "support@thoughtbot.com" 10 | s.homepage = "http://github.com/thoughtbot/capybara-webkit" 11 | s.summary = "Headless Webkit driver for Capybara" 12 | s.license = 'MIT' 13 | 14 | s.files = `git ls-files`.split("\n") 15 | s.test_files = `git ls-files -- {spec,features}/*`.split("\n") 16 | s.require_path = "lib" 17 | 18 | s.extensions = "extconf.rb" 19 | 20 | s.required_ruby_version = ">= 1.9.0" 21 | 22 | s.requirements << "Qt >= 4.8" 23 | 24 | s.add_runtime_dependency("capybara", ">= 2.3", "< 4.0") 25 | s.add_runtime_dependency("json") 26 | 27 | s.add_development_dependency("rspec", "~> 3.5") 28 | # Sinatra is used by Capybara's TestApp 29 | s.add_development_dependency("sinatra") 30 | s.add_development_dependency("mini_magick") 31 | s.add_development_dependency("rake", "< 12.0.0") 32 | s.add_development_dependency("appraisal") 33 | s.add_development_dependency("launchy") 34 | end 35 | 36 | -------------------------------------------------------------------------------- /extconf.rb: -------------------------------------------------------------------------------- 1 | require "mkmf" 2 | 3 | $CPPFLAGS = "" 4 | 5 | dir_config("gl") 6 | dir_config("zlib") 7 | 8 | include_path = $CPPFLAGS.gsub("-I", "").strip 9 | libs = $LIBPATH.map { |path| "-L#{path}"}.join(" ").strip 10 | 11 | unless include_path.empty? 12 | ENV["CAPYBARA_WEBKIT_INCLUDE_PATH"] = include_path 13 | end 14 | 15 | unless libs.empty? 16 | ENV["CAPYBARA_WEBKIT_LIBS"] = libs 17 | end 18 | 19 | require File.join(File.expand_path(File.dirname(__FILE__)), "lib", "capybara_webkit_builder") 20 | CapybaraWebkitBuilder.build_all 21 | -------------------------------------------------------------------------------- /gemfiles/2.15.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "json", "< 2.0", platforms: [:ruby_19, :jruby_19] 6 | gem "capybara", "~> 2.15.0" 7 | gem "addressable", "< 2.5.0", platforms: [:ruby_19, :jruby_19] 8 | gem "nokogiri", "< 1.7.0", platforms: [:ruby_19, :jruby_19] 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/master.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "capybara", github: "teamcapybara/capybara" 6 | gem "puma" 7 | 8 | gemspec path: "../" 9 | -------------------------------------------------------------------------------- /lib/capybara-webkit.rb: -------------------------------------------------------------------------------- 1 | require "capybara/webkit" 2 | -------------------------------------------------------------------------------- /lib/capybara/webkit.rb: -------------------------------------------------------------------------------- 1 | require "capybara" 2 | 3 | module Capybara 4 | module Webkit 5 | def self.configure(&block) 6 | Capybara::Webkit::Configuration.modify(&block) 7 | end 8 | end 9 | end 10 | 11 | require "capybara/webkit/driver" 12 | require "capybara/webkit/configuration" 13 | 14 | Capybara.register_driver :webkit do |app| 15 | Capybara::Webkit::Driver.new(app, Capybara::Webkit::Configuration.to_hash) 16 | end 17 | 18 | Capybara.register_driver :webkit_debug do |app| 19 | warn "[DEPRECATION] The webkit_debug driver is deprecated. " \ 20 | "Please use Capybara::Webkit.configure instead:\n\n" \ 21 | " Capybara::Webkit.configure do |config|\n" \ 22 | " config.debug = true\n" \ 23 | " end\n\n" \ 24 | "This option is global and can be configured once" \ 25 | " (not in a `before` or `setup` block)." 26 | 27 | Capybara::Webkit::Driver.new( 28 | app, 29 | Capybara::Webkit::Configuration.to_hash.merge(debug: true) 30 | ) 31 | end 32 | -------------------------------------------------------------------------------- /lib/capybara/webkit/configuration.rb: -------------------------------------------------------------------------------- 1 | module Capybara 2 | module Webkit 3 | class Configuration 4 | class << self 5 | private 6 | 7 | def instance 8 | @instance ||= new 9 | end 10 | end 11 | 12 | def self.to_hash 13 | instance.freeze.to_hash 14 | end 15 | 16 | def self.modify 17 | if instance.frozen? 18 | raise "All configuration must take place before the driver starts" 19 | else 20 | yield instance 21 | end 22 | end 23 | 24 | attr_accessor :allowed_urls 25 | attr_writer :block_unknown_urls 26 | attr_accessor :blocked_urls 27 | attr_accessor :debug 28 | attr_writer :ignore_ssl_errors 29 | attr_accessor :proxy 30 | attr_accessor :stderr 31 | attr_accessor :timeout 32 | attr_writer :skip_image_loading 33 | attr_accessor :raise_javascript_errors 34 | 35 | def initialize 36 | @allowed_urls = [] 37 | @blocked_urls = [] 38 | @block_unknown_urls = false 39 | @debug = false 40 | @ignore_ssl_errors = false 41 | @proxy = nil 42 | @skip_image_loading = false 43 | @stderr = $stderr 44 | @timeout = -1 45 | @raise_javascript_errors = false 46 | end 47 | 48 | def allow_url(url) 49 | @allowed_urls << url 50 | end 51 | 52 | def block_url(url) 53 | @blocked_urls << url 54 | end 55 | 56 | def block_unknown_urls 57 | @block_unknown_urls = true 58 | end 59 | 60 | def block_unknown_urls? 61 | @block_unknown_urls 62 | end 63 | 64 | def allow_unknown_urls 65 | allow_url("*") 66 | end 67 | 68 | def ignore_ssl_errors 69 | @ignore_ssl_errors = true 70 | end 71 | 72 | def ignore_ssl_errors? 73 | @ignore_ssl_errors 74 | end 75 | 76 | def skip_image_loading 77 | @skip_image_loading = true 78 | end 79 | 80 | def skip_image_loading? 81 | @skip_image_loading 82 | end 83 | 84 | def use_proxy(proxy) 85 | @proxy = proxy 86 | end 87 | 88 | def to_hash 89 | { 90 | allowed_urls: allowed_urls, 91 | block_unknown_urls: block_unknown_urls?, 92 | blocked_urls: blocked_urls, 93 | debug: debug, 94 | ignore_ssl_errors: ignore_ssl_errors?, 95 | proxy: proxy, 96 | skip_image_loading: skip_image_loading?, 97 | stderr: stderr, 98 | timeout: timeout, 99 | raise_javascript_errors: raise_javascript_errors, 100 | } 101 | end 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /lib/capybara/webkit/connection.rb: -------------------------------------------------------------------------------- 1 | require 'socket' 2 | require 'timeout' 3 | require 'thread' 4 | 5 | module Capybara::Webkit 6 | class Connection 7 | def initialize(options = {}) 8 | @socket = nil 9 | if options.has_key?(:socket_class) 10 | warn '[DEPRECATION] The Capybara::Webkit::Connection `socket_class` ' \ 11 | 'option is deprecated without replacement.' 12 | @socket_class = options[:socket_class] 13 | else 14 | @socket_class = TCPSocket 15 | end 16 | @server = options[:server] 17 | start_server 18 | connect 19 | end 20 | 21 | def puts(string) 22 | @socket.puts string 23 | end 24 | 25 | def print(string) 26 | @socket.print string 27 | end 28 | 29 | def gets 30 | response = "" 31 | until response.match(/\n/) do 32 | response += read(1) 33 | end 34 | response 35 | end 36 | 37 | def read(length) 38 | response = "" 39 | begin 40 | while response.length < length do 41 | response += @socket.read_nonblock(length - response.length) 42 | end 43 | rescue IO::WaitReadable 44 | Thread.new { IO.select([@socket]) }.join 45 | retry 46 | end 47 | response 48 | end 49 | 50 | def restart 51 | @socket = nil 52 | start_server 53 | connect 54 | end 55 | 56 | def port 57 | @server.port 58 | end 59 | 60 | def pid 61 | @server.pid 62 | end 63 | 64 | private 65 | 66 | def start_server 67 | @server.start 68 | end 69 | 70 | def connect 71 | Timeout.timeout(5) do 72 | while @socket.nil? 73 | attempt_connect 74 | end 75 | end 76 | end 77 | 78 | def attempt_connect 79 | @socket = @socket_class.open("127.0.0.1", port) 80 | if defined?(Socket::TCP_NODELAY) 81 | @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true) 82 | end 83 | rescue Errno::ECONNREFUSED 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/capybara/webkit/cookie_jar.rb: -------------------------------------------------------------------------------- 1 | require 'webrick/cookie' 2 | 3 | # A simple cookie jar implementation. 4 | # Does not take special cookie attributes 5 | # into account like expire, max-age, httponly, secure 6 | class Capybara::Webkit::CookieJar 7 | attr_reader :browser 8 | 9 | def initialize(browser) 10 | @browser = browser 11 | end 12 | 13 | def [](*args) 14 | cookie = find(*args) 15 | cookie && cookie.value 16 | end 17 | 18 | def find(name, domain = nil, path = "/") 19 | # we are sorting by path size because more specific paths take 20 | # precendence 21 | cookies.sort_by { |c| -c.path.size }.find { |c| 22 | c.name.downcase == name.downcase && 23 | (!domain || valid_domain?(c, domain)) && 24 | (!path || valid_path?(c, path)) 25 | } 26 | end 27 | 28 | protected 29 | 30 | def valid_domain?(cookie, domain) 31 | ends_with?(("." + domain).downcase, 32 | normalize_domain(cookie.domain).downcase) 33 | end 34 | 35 | def normalize_domain(domain) 36 | domain = "." + domain unless domain[0,1] == "." 37 | domain 38 | end 39 | 40 | def valid_path?(cookie, path) 41 | starts_with?(path, cookie.path) 42 | end 43 | 44 | def ends_with?(str, suffix) 45 | str[-suffix.size..-1] == suffix 46 | end 47 | 48 | def starts_with?(str, prefix) 49 | str[0, prefix.size] == prefix 50 | end 51 | 52 | def cookies 53 | browser.get_cookies.map { |c| WEBrick::Cookie.parse_set_cookie(c) } 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/capybara/webkit/errors.rb: -------------------------------------------------------------------------------- 1 | module Capybara::Webkit 2 | class InvalidResponseError < StandardError 3 | end 4 | 5 | class NoResponseError < StandardError 6 | end 7 | 8 | class NodeNotAttachedError < Capybara::ElementNotFound 9 | end 10 | 11 | class ClickFailed < StandardError 12 | end 13 | 14 | class TimeoutError < Timeout::Error 15 | end 16 | 17 | class NoSuchWindowError < StandardError 18 | end 19 | 20 | class ConnectionError < StandardError 21 | end 22 | 23 | class ModalNotFound < StandardError 24 | end 25 | 26 | class CrashError < StandardError 27 | end 28 | 29 | class JsonError 30 | def initialize(response) 31 | error = JSON.parse response 32 | 33 | @class_name = error['class'] 34 | @message = error['message'] 35 | end 36 | 37 | def exception 38 | error_class.new @message 39 | end 40 | 41 | private 42 | 43 | def error_class 44 | Capybara::Webkit.const_get @class_name 45 | end 46 | end 47 | 48 | class JavaScriptError < StandardError 49 | def initialize(errors) 50 | @javascript_errors = errors 51 | super(errors.join(",")) 52 | end 53 | 54 | attr_reader :javascript_errors 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/capybara/webkit/matchers.rb: -------------------------------------------------------------------------------- 1 | module Capybara 2 | module Webkit 3 | module RspecMatchers 4 | RSpec::Matchers.define :have_errors do |expected| 5 | match do |actual| 6 | actual = resolve(actual) 7 | actual.error_messages.any? 8 | end 9 | 10 | #RSpec 2 compatability 11 | send(respond_to?(:failure_message) ? :failure_message : :failure_message_for_should) do |_actual| 12 | "Expected Javascript errors, but there were none." 13 | end 14 | 15 | #RSpec 2 compatability 16 | send(respond_to?(:failure_message_when_negated) ? :failure_message_when_negated : :failure_message_for_should_not) do |actual| 17 | actual = resolve(actual) 18 | "Expected no Javascript errors, got:\n#{error_messages_for(actual)}" 19 | end 20 | 21 | def error_messages_for(obj) 22 | obj.error_messages.map do |m| 23 | " - #{m[:message]}" 24 | end.join("\n") 25 | end 26 | 27 | def resolve(actual) 28 | if actual.respond_to? :page 29 | actual.page.driver 30 | elsif actual.respond_to? :driver 31 | actual.driver 32 | else 33 | actual 34 | end 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/capybara/webkit/server.rb: -------------------------------------------------------------------------------- 1 | require "open3" 2 | require "thread" 3 | 4 | module Capybara 5 | module Webkit 6 | class Server 7 | SERVER_PATH = File.expand_path("../../../../bin/webkit_server", __FILE__) 8 | WEBKIT_SERVER_START_TIMEOUT = 15 9 | 10 | attr_reader :port, :pid 11 | 12 | def initialize(options = {}) 13 | if options.has_key?(:stderr) 14 | @output_target = options[:stderr] 15 | elsif options.has_key?(:stdout) 16 | warn "[DEPRECATION] The Capybara::Webkit::Connection `stdout` " \ 17 | "option is deprecated. Please use `stderr` instead." 18 | @output_target = options[:stdout] 19 | else 20 | @output_target = $stderr 21 | end 22 | end 23 | 24 | def start 25 | open_pipe 26 | discover_port 27 | discover_pid 28 | forward_output_in_background_thread 29 | register_shutdown_hook 30 | end 31 | 32 | private 33 | 34 | def open_pipe 35 | if IO.respond_to?(:popen4) 36 | @pid, 37 | @pipe_stdin, 38 | @pipe_stdout, 39 | @pipe_stderr = IO.popen4(SERVER_PATH) 40 | else 41 | @pipe_stdin, 42 | @pipe_stdout, 43 | @pipe_stderr, 44 | @wait_thr = Open3.popen3(SERVER_PATH) 45 | end 46 | end 47 | 48 | def discover_port 49 | if IO.select([@pipe_stdout], nil, nil, WEBKIT_SERVER_START_TIMEOUT) 50 | @port = parse_port(@pipe_stdout.first) 51 | else 52 | raise( 53 | ConnectionError, 54 | "#{SERVER_PATH} failed to start after " \ 55 | "#{WEBKIT_SERVER_START_TIMEOUT} seconds.", 56 | ) 57 | end 58 | end 59 | 60 | def parse_port(line) 61 | if match = line.to_s.match(/listening on port: (\d+)/) 62 | match[1].to_i 63 | else 64 | raise ConnectionError, "#{SERVER_PATH} failed to start." 65 | end 66 | end 67 | 68 | def discover_pid 69 | @pid ||= @wait_thr[:pid] 70 | end 71 | 72 | def forward_output_in_background_thread 73 | Thread.new do 74 | Thread.current.abort_on_exception = true 75 | IO.copy_stream(@pipe_stderr, @output_target) if @output_target 76 | end 77 | end 78 | 79 | def register_shutdown_hook 80 | @owner_pid = Process.pid 81 | at_exit do 82 | if Process.pid == @owner_pid 83 | kill_process 84 | end 85 | end 86 | end 87 | 88 | def kill_process 89 | if @pid 90 | if RUBY_PLATFORM =~ /mingw32/ 91 | Process.kill(9, @pid) 92 | else 93 | Process.kill("INT", @pid) 94 | end 95 | Process.wait(@pid) 96 | @wait_thr.join if @wait_thr 97 | end 98 | rescue Errno::ESRCH, Errno::ECHILD 99 | # This just means that the webkit_server process has already ended 100 | ensure 101 | @pid = @wait_thr = nil 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/capybara/webkit/version.rb: -------------------------------------------------------------------------------- 1 | module Capybara 2 | module Driver 3 | class Webkit 4 | VERSION = "1.15.1".freeze 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/capybara_webkit_builder.rb: -------------------------------------------------------------------------------- 1 | require 'etc' 2 | require "fileutils" 3 | require "rbconfig" 4 | require "shellwords" 5 | 6 | module CapybaraWebkitBuilder 7 | extend self 8 | 9 | SUCCESS_STATUS = 0 10 | COMMAND_NOT_FOUND_STATUS = 127 11 | 12 | def make_bin 13 | ENV['MAKE'] || 'make' 14 | end 15 | 16 | def qmake_bin 17 | ENV['QMAKE'] || default_qmake_binary 18 | end 19 | 20 | def default_qmake_binary 21 | case RbConfig::CONFIG['host_os'] 22 | when /freebsd/ 23 | "qmake-qt4" 24 | when /openbsd/ 25 | "qmake-qt5" 26 | else 27 | "qmake" 28 | end 29 | end 30 | 31 | def sh(command) 32 | system(command) 33 | success = $?.exitstatus == SUCCESS_STATUS 34 | if $?.exitstatus == COMMAND_NOT_FOUND_STATUS 35 | puts "Command '#{command}' not available" 36 | elsif !success 37 | puts "Command '#{command}' failed" 38 | end 39 | success 40 | end 41 | 42 | def makefile(*configs) 43 | configs += default_configs 44 | configs = configs.map { |config| config.shellescape}.join(" ") 45 | sh("#{qmake_bin} #{configs}") 46 | end 47 | 48 | def qmake 49 | make "qmake" 50 | end 51 | 52 | def path_to_binary 53 | if Gem.win_platform? 54 | "src/debug/webkit_server.exe" 55 | else 56 | "src/webkit_server" 57 | end 58 | end 59 | 60 | def build 61 | make or return false 62 | 63 | FileUtils.mkdir("bin") unless File.directory?("bin") 64 | FileUtils.cp(path_to_binary, "bin", :preserve => true) 65 | end 66 | 67 | def clean 68 | File.open("Makefile", "w") do |file| 69 | file.print "all:\n\t@echo ok\ninstall:\n\t@echo ok" 70 | end 71 | end 72 | 73 | def default_configs 74 | configs = [] 75 | libpath = ENV["CAPYBARA_WEBKIT_LIBS"] 76 | cppflags = ENV["CAPYBARA_WEBKIT_INCLUDE_PATH"] 77 | if libpath 78 | configs << "LIBS += #{libpath}" 79 | end 80 | if cppflags 81 | configs << "INCLUDEPATH += #{cppflags}" 82 | end 83 | configs 84 | end 85 | 86 | def build_all 87 | makefile && 88 | qmake && 89 | build && 90 | clean 91 | end 92 | 93 | def make(target = "") 94 | job_count = Etc.respond_to?(:nprocessors) ? Etc.nprocessors : 1 95 | env_hide('CDPATH') { sh("#{make_bin} #{target} --jobs=#{job_count}") } 96 | end 97 | 98 | def env_hide(name) 99 | @stored_env ||= {} 100 | @stored_env[name] = ENV.delete(name) 101 | 102 | yield 103 | ensure 104 | ENV[name] = @stored_env[name] 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /spec/browser_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'self_signed_ssl_cert' 3 | require 'stringio' 4 | require 'capybara/webkit/driver' 5 | require 'socket' 6 | require 'base64' 7 | 8 | describe Capybara::Webkit::Browser do 9 | 10 | let(:server) { Capybara::Webkit::Server.new } 11 | let(:connection) { Capybara::Webkit::Connection.new(server: server) } 12 | let(:browser) { Capybara::Webkit::Browser.new(connection) } 13 | 14 | describe "forking", skip_on_windows: true, skip_on_jruby: true do 15 | it "only shuts down the server from the main process" do 16 | browser.reset! 17 | pid = fork {} 18 | Process.wait(pid) 19 | expect { browser.reset! }.not_to raise_error 20 | end 21 | end 22 | 23 | it "doesn't try to read an empty response" do 24 | connection = double("connection", puts: nil, print: nil) 25 | allow(connection).to receive(:gets).and_return("ok\n", "0\n") 26 | allow(connection).to receive(:read).and_raise(StandardError.new("tried to read empty response")) 27 | 28 | browser = Capybara::Webkit::Browser.new(connection) 29 | 30 | expect { browser.visit("/") }.not_to raise_error 31 | end 32 | 33 | describe '#command' do 34 | context 'non-ok response' do 35 | it 'raises an error of given class' do 36 | error_json = '{"class": "ClickFailed"}' 37 | 38 | expect(connection).to receive(:gets).ordered.and_return 'error' 39 | expect(connection).to receive(:gets).ordered.and_return error_json.bytesize 40 | allow(connection).to receive(:read).and_return(error_json) 41 | 42 | expect { browser.command 'blah', 'meh' }.to raise_error(Capybara::Webkit::ClickFailed) 43 | end 44 | end 45 | end 46 | 47 | describe "#reset!" do 48 | it "resets to the default state" do 49 | connection = double("connection", puts: nil, print: nil) 50 | allow(connection).to receive(:gets).and_return("ok\n", "{}\n") 51 | 52 | browser = Capybara::Webkit::Browser.new(connection) 53 | browser.set_raise_javascript_errors(true) 54 | 55 | expect(browser.raise_javascript_errors?).to be true 56 | 57 | browser.reset! 58 | 59 | expect(browser.raise_javascript_errors?).to be false 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/capybara_webkit_builder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'capybara_webkit_builder' 3 | 4 | describe CapybaraWebkitBuilder do 5 | let(:builder) { CapybaraWebkitBuilder } 6 | 7 | it "will use the env variable for #make_bin" do 8 | with_env_vars("MAKE" => "fake_make") do 9 | expect(builder.make_bin).to eq "fake_make" 10 | end 11 | end 12 | 13 | it "will use the env variable for #qmake_bin" do 14 | with_env_vars("QMAKE" => "fake_qmake") do 15 | expect(builder.qmake_bin).to eq "fake_qmake" 16 | end 17 | end 18 | 19 | it "defaults the #make_bin" do 20 | with_env_vars("MAKE_BIN" => nil) do 21 | expect(builder.make_bin).to eq 'make' 22 | end 23 | end 24 | 25 | it "defaults the #qmake_bin" do 26 | with_env_vars("QMAKE" => nil) do 27 | expect(builder.qmake_bin).to eq 'qmake' 28 | end 29 | end 30 | end 31 | 32 | -------------------------------------------------------------------------------- /spec/configuration_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Capybara::Webkit::Configuration do 4 | it "returns a hash and then prevents future modification" do 5 | Capybara::Webkit.configure do |config| 6 | config.debug = true 7 | end 8 | 9 | result = Capybara::Webkit::Configuration.to_hash 10 | 11 | expect(result).to include(debug: true) 12 | expect { Capybara::Webkit.configure {} }.to raise_error( 13 | "All configuration must take place before the driver starts" 14 | ) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/connection_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'capybara/webkit/connection' 3 | 4 | describe Capybara::Webkit::Connection do 5 | it "sets appropriate options on its socket" do 6 | socket = double("socket") 7 | server = double(:Server, start: nil, port: 123) 8 | allow(TCPSocket).to receive(:open).and_return(socket) 9 | if defined?(Socket::TCP_NODELAY) 10 | expect(socket).to receive(:setsockopt). 11 | with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true) 12 | else 13 | expect(socket).not_to receive(:setsockopt) 14 | end 15 | 16 | Capybara::Webkit::Connection.new(server: server) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/cookie_jar_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'capybara/webkit/cookie_jar' 3 | 4 | describe Capybara::Webkit::CookieJar do 5 | let(:browser) { 6 | browser = double("Browser") 7 | allow(browser).to receive(:get_cookies) { [ 8 | "cookie1=1; domain=.example.org; path=/", 9 | "cookie1=2; domain=.example.org; path=/dir1/", 10 | "cookie1=3; domain=.facebook.com; path=/", 11 | "cookie2=4; domain=.sub1.example.org; path=/", 12 | ] } 13 | browser 14 | } 15 | 16 | subject { Capybara::Webkit::CookieJar.new(browser) } 17 | 18 | describe "#find" do 19 | it "returns a cookie object" do 20 | expect(subject.find("cookie1", "www.facebook.com").domain).to eq ".facebook.com" 21 | end 22 | 23 | it "returns the right cookie for every given domain/path" do 24 | expect(subject.find("cookie1", "example.org").value).to eq "1" 25 | expect(subject.find("cookie1", "www.facebook.com").value).to eq "3" 26 | expect(subject.find("cookie2", "sub1.example.org").value).to eq "4" 27 | end 28 | 29 | it "does not return a cookie from other domain" do 30 | expect(subject.find("cookie2", "www.example.org")).to eq nil 31 | end 32 | 33 | it "respects path precedence rules" do 34 | expect(subject.find("cookie1", "www.example.org").value).to eq "1" 35 | expect(subject.find("cookie1", "www.example.org", "/dir1/123").value).to eq "2" 36 | end 37 | end 38 | 39 | describe "#[]" do 40 | it "returns the first matching cookie's value" do 41 | expect(subject["cookie1", "example.org"]).to eq "1" 42 | end 43 | 44 | it "returns nil if no cookie is found" do 45 | expect(subject["notexisting"]).to eq nil 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/driver_rendering_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'capybara/webkit/driver' 3 | require 'mini_magick' 4 | 5 | describe Capybara::Webkit::Driver, "rendering an image" do 6 | include AppRunner 7 | 8 | let(:driver) do 9 | driver_for_html(<<-HTML) 10 | 11 | 12 |

Hello World

13 | 14 | 15 | HTML 16 | end 17 | 18 | before(:each) do 19 | # Set up the tmp directory and file name 20 | tmp_dir = File.join(PROJECT_ROOT, 'tmp') 21 | FileUtils.mkdir_p tmp_dir 22 | @file_name = File.join(tmp_dir, 'render-test.png') 23 | driver.visit("#{AppRunner.app_host}/") 24 | end 25 | 26 | def render(options) 27 | FileUtils.rm_f @file_name 28 | driver.save_screenshot @file_name, options 29 | 30 | @image = MiniMagick::Image.open @file_name 31 | end 32 | 33 | context "with default options" do 34 | before { render({}) } 35 | 36 | it "should be a PNG" do 37 | expect(@image[:format]).to eq "PNG" 38 | end 39 | 40 | it "width default to 1000px (with 15px less for the scrollbar)" do 41 | expect(@image[:width]).to be < 1001 42 | expect(@image[:width]).to be > 1000-17 43 | end 44 | 45 | it "height should be at least 10px" do 46 | expect(@image[:height]).to be >= 10 47 | end 48 | end 49 | 50 | context "with dimensions set larger than necessary" do 51 | before { render(:width => 500, :height => 400) } 52 | 53 | it "width should match the width given" do 54 | expect(@image[:width]).to eq 500 55 | end 56 | 57 | it "height should match the height given" do 58 | expect(@image[:height]).to eq 400 59 | end 60 | 61 | it "should reset window dimensions to their default value" do 62 | expect(driver.evaluate_script('window.innerWidth')).to eq 1680 63 | expect(driver.evaluate_script('window.innerHeight')).to eq 1050 64 | end 65 | end 66 | 67 | context "with dimensions set smaller than the document's default" do 68 | before { render(:width => 50, :height => 10) } 69 | 70 | it "width should be greater than the width given" do 71 | expect(@image[:width]).to be > 50 72 | end 73 | 74 | it "height should be greater than the height given" do 75 | expect(@image[:height]).to be > 10 76 | end 77 | 78 | it "should restore viewport dimensions after rendering" do 79 | expect(driver.evaluate_script('window.innerWidth')).to eq 1680 80 | expect(driver.evaluate_script('window.innerHeight')).to eq 1050 81 | end 82 | end 83 | 84 | context "with a custom viewport size" do 85 | before { driver.resize_window(800, 600) } 86 | 87 | it "should restore viewport dimensions after rendering" do 88 | render({}) 89 | expect(driver.evaluate_script('window.innerWidth')).to eq 800 90 | expect(driver.evaluate_script('window.innerHeight')).to eq 600 91 | end 92 | end 93 | 94 | context "with invalid filepath" do 95 | before do 96 | @file_name = File.dirname(@file_name) 97 | end 98 | 99 | it "raises an InvalidResponseError" do 100 | expect { render({}) }.to raise_error(Capybara::Webkit::InvalidResponseError) 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /spec/driver_resize_window_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'capybara/webkit/driver' 3 | 4 | describe Capybara::Webkit::Driver, "#resize_window(width, height)" do 5 | include AppRunner 6 | 7 | let(:driver) do 8 | driver_for_html(<<-HTML) 9 | 10 | 11 |

UNKNOWN

12 | 13 | 18 | 19 | 20 | 21 | HTML 22 | end 23 | 24 | DEFAULT_DIMENTIONS = "[1680x1050]" 25 | 26 | it "resizes the window to the specified size" do 27 | driver.visit("#{AppRunner.app_host}/") 28 | 29 | driver.resize_window(800, 600) 30 | expect(driver.html).to include("[800x600]") 31 | 32 | driver.resize_window(300, 100) 33 | expect(driver.html).to include("[300x100]") 34 | end 35 | 36 | it "resizes the window to the specified size even before the document has loaded" do 37 | driver.resize_window(800, 600) 38 | driver.visit("#{AppRunner.app_host}/") 39 | expect(driver.html).to include("[800x600]") 40 | end 41 | 42 | it "resets the window to the default size when the driver is reset" do 43 | driver.resize_window(800, 600) 44 | driver.reset! 45 | driver.visit("#{AppRunner.app_host}/") 46 | expect(driver.html).to include(DEFAULT_DIMENTIONS) 47 | end 48 | 49 | it "resizes windows by handle" do 50 | driver.visit("#{AppRunner.app_host}/") 51 | driver.open_new_window 52 | driver.visit("#{AppRunner.app_host}/") 53 | 54 | driver.resize_window_to(driver.window_handles.first, 800, 600) 55 | driver.resize_window_to(driver.window_handles.last, 400, 300) 56 | 57 | expect(driver.window_size(driver.window_handles.first)).to eq [800, 600] 58 | expect(driver.window_size(driver.window_handles.last)).to eq [400, 300] 59 | end 60 | 61 | it "maximizes a window" do 62 | driver.visit("#{AppRunner.app_host}/") 63 | driver.resize_window(400, 300) 64 | driver.maximize_window(driver.current_window_handle) 65 | width, height = *driver.window_size(driver.current_window_handle) 66 | 67 | expect(width).to be > 400 68 | expect(height).to be > 300 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/errors_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Webkit::JsonError do 4 | let(:error) { described_class.new '{"class": "ClickFailed", "message": "Error clicking this element"}' } 5 | 6 | subject { error.exception } 7 | 8 | it { should be_an_instance_of Capybara::Webkit::ClickFailed } 9 | 10 | it { expect(subject.message).to eq 'Error clicking this element' } 11 | end 12 | -------------------------------------------------------------------------------- /spec/fixtures/exit_text.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | # require 'selenium-webdriver' 3 | 4 | RSpec.describe Capybara::Webkit::Driver do 5 | it "should exit with a zero exit status" do 6 | browser = Capybara::Webkit::Driver.new(TestApp).browser 7 | expect(true).to eq(true) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/fixtures/fake_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sleep 1 4 | -------------------------------------------------------------------------------- /spec/node_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: UTF-8 -*- 2 | 3 | require "spec_helper" 4 | require "capybara/webkit/driver" 5 | require "base64" 6 | require "self_signed_ssl_cert" 7 | 8 | describe Capybara::Webkit::Node do 9 | include AppRunner 10 | 11 | def visit(path, driver = self.driver) 12 | driver.visit(url(path)) 13 | end 14 | 15 | def url(path) 16 | "#{AppRunner.app_host}#{path}" 17 | end 18 | 19 | context "html app" do 20 | let(:driver) do 21 | driver_for_html(<<-HTML) 22 | 23 | 24 | Hello HTML 25 | 26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 | 42 | 43 | 46 | 47 | 48 | HTML 49 | end 50 | 51 | before { visit("/") } 52 | 53 | context "Node#[]" do 54 | it "gets boolean properties" do 55 | box1 = driver.find_css('input[name="checkedbox"]').first 56 | box2 = driver.find_css('input[name="uncheckedbox"]').first 57 | box3 = driver.find_css('input[name="falsecheckedbox"]').first 58 | expect(box1["checked"]).to eq true 59 | expect(box2["checked"]).to eq false 60 | expect(box3["checked"]).to eq true 61 | box1.set(false) 62 | expect(box1["checked"]).to eq false 63 | end 64 | 65 | it "prefers property over attribute" do 66 | input = driver.find_css('input[name="foo"]').first 67 | expect(input["value"]).to eq "bar" 68 | input.set("new value") 69 | expect(input["value"]).to eq "new value" 70 | end 71 | 72 | it "returns attribute when property is an object" do 73 | input = driver.find_css('input[name="styled"]').first 74 | expect(input["style"]).to eq "font-size: 150%;" 75 | end 76 | end 77 | 78 | context "Node#visible" do 79 | it "correctly analyzes visibility CSS" do 80 | expect(driver.find_css('#hidden').first.visible?).to be false 81 | expect(driver.find_css('#visible').first.visible?).to be true 82 | expect(driver.find_css('#nested_visible').first.visible?).to be true 83 | end 84 | 85 | it "correctly analyzes display: none CSS" do 86 | expect(driver.find_css('#not_displayed').first.visible?).to be false 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/selenium_compatibility_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Webkit, 'compatibility with selenium' do 4 | include AppRunner 5 | 6 | it 'generates the same events as selenium when filling out forms', selenium_compatibility: true do 7 | run_application_for_html(<<-HTML) 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 |
21 | 35 | 36 | HTML 37 | 38 | compare_events_for_drivers(:reusable_webkit, :selenium) do 39 | visit "/" 40 | fill_in "One", :with => "some value" 41 | fill_in "One", :with => "a new value" 42 | fill_in "Two", :with => "other value" 43 | fill_in "Three", :with => "readonly value" 44 | fill_in "Textarea", :with => "last value" 45 | select "some option", :from => "five" 46 | click_button "Submit" 47 | end 48 | end 49 | 50 | it 'generates the same requests and responses as selenium', selenium_compatibility: true do 51 | requests = [] 52 | 53 | app = Class.new(ExampleApp) do 54 | before do 55 | unless request.path_info =~ /favicon\.ico/ 56 | requests << request.path_info 57 | end 58 | end 59 | 60 | get '/' do 61 | <<-HTML 62 | 63 | 64 | 65 | 66 | 67 | Original 68 | 69 | HTML 70 | end 71 | 72 | get '/:script.js' do 73 | '' 74 | end 75 | 76 | get '/requests' do 77 | <<-HTML 78 | 79 | 80 | #{requests.sort.join("\n")} 81 | 82 | 83 | HTML 84 | end 85 | end 86 | 87 | run_application app 88 | 89 | compare_for_drivers(:reusable_webkit, :selenium) do |session| 90 | responses = [] 91 | session.visit "/" 92 | responses << record_response(session) 93 | session.visit "/" 94 | responses << record_response(session) 95 | session.visit "/requests" 96 | responses << record_response(session) 97 | requests.clear 98 | responses.join("\n\n") 99 | end 100 | end 101 | 102 | def compare_events_for_drivers(first, second, &block) 103 | compare_for_drivers(first, second) do |session| 104 | session.instance_eval(&block) 105 | session.evaluate_script("window.log") 106 | end 107 | end 108 | 109 | def compare_for_drivers(first, second, &block) 110 | expect(for_driver(first, &block)).to eq for_driver(second, &block) 111 | end 112 | 113 | def for_driver(name, &block) 114 | session = Capybara::Session.new(name, AppRunner.app) 115 | result = yield session 116 | result 117 | end 118 | 119 | def record_response(session) 120 | [ 121 | session.current_url, 122 | normalize_body(session.body) 123 | ].join("\n") 124 | end 125 | 126 | def normalize_body(body) 127 | if body.length > 0 128 | Nokogiri::HTML.parse(body).at("//body").text.strip 129 | else 130 | "(empty)" 131 | end 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /spec/self_signed_ssl_cert.rb: -------------------------------------------------------------------------------- 1 | require 'openssl' 2 | 3 | pem = <<-PEM_ENCODED 4 | -----BEGIN PRIVATE KEY----- 5 | MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKW+grT6YW3gv79y 6 | P9JkQDtm3cDSUhAhd/TEyRBt/pSKz3pNSygsleBJl2d7g8k0fteec95a7YnYRKGH 7 | XhIpUOvl/3uaV2NVipqxwB+Z+0M+7HegxL3e4unaRFy9kf9/UXJzmuA9BTMLrm/w 8 | IoW17f+dz7BIFZhCvRurkrmvzraNAgMBAAECgYBv4uB3bYJx20N16Jk+3OAjeXh/ 9 | Hzu4me9Rc7pLdgVinyYaaK0wrJBsfSFRASdgnyh1RAjx9K3f3PfPlwMg/XUbA6Yd 10 | YOYlMnBUwCJX09TH8RFFCzJzbBylpk/sTF1geICln2O2BloT2cM24PlEPvyz1xLa 11 | XvxCOnJJfgNU1K6kvQJBANcEVyOMJ9RBfI8gj1o7S70J9yJRI4jvqxuvcOxJBCi6 12 | CDatkh/getHswsE3sLj25VhrNsi6UQcN8Bjm8Yjt8BsCQQDFVe0uCwobseprMOuW 13 | dPU4+saN1cFnIT5Gp0iwYRPinjUlkh6H/AuUtUulKFXVmxk1KElpp1E3bxpCDgCp 14 | oe53AkArO1Mt8Ys8kSIzQO+xy8RRsQRAoSHM8atsuJyy1YeBjM4D+GguApuPQ9Rw 15 | tvrQZcv9OCleuJ98FKBW0XB1AKpLAkEAmOR3bJofDdAuWTjA/4TEzo32MsRwIZBv 16 | KNzJg+bjOkzrzp1EzIVrD5/b6S20O1j9EeOR5as+UN3jEVS6DLQrBwJAe5OTrDiQ 17 | vKtUaAwquC4f4Ia05KwJw+vFrPVaOqgc4QLdxRwx4PfV/Uw3OOqMolpPAvpUi9JI 18 | LAwIaTtCvo18OQ== 19 | -----END PRIVATE KEY----- 20 | -----BEGIN CERTIFICATE----- 21 | MIICgDCCAemgAwIBAgIJANWcyeZB2ql1MA0GCSqGSIb3DQEBBQUAMFkxCzAJBgNV 22 | BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX 23 | aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xMTA5MjgyMDIx 24 | MTFaFw0xMjA5MjcyMDIxMTFaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l 25 | LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV 26 | BAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApb6CtPph 27 | beC/v3I/0mRAO2bdwNJSECF39MTJEG3+lIrPek1LKCyV4EmXZ3uDyTR+155z3lrt 28 | idhEoYdeEilQ6+X/e5pXY1WKmrHAH5n7Qz7sd6DEvd7i6dpEXL2R/39RcnOa4D0F 29 | Mwuub/AihbXt/53PsEgVmEK9G6uSua/Oto0CAwEAAaNQME4wHQYDVR0OBBYEFBAm 30 | x19zpY8M8FEcMXB4WQeUhqIiMB8GA1UdIwQYMBaAFBAmx19zpY8M8FEcMXB4WQeU 31 | hqIiMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAcznCusWS5Ws77IUl 32 | b87vdfCPVphICyfGHGWhHp3BZ3WLOphauAMdOYIiJGtPExyWr4DYpzbvx0+Ljg7G 33 | 2FNaI0QRXqbR5ccpvwm6KELmU0XDhykNaXiXSdnvIanr3z/hZ5e03mXAywo+nGQj 34 | UYTVCb6g/uHVNzXq1NgHGuqogKY= 35 | -----END CERTIFICATE----- 36 | PEM_ENCODED 37 | 38 | key = OpenSSL::PKey::RSA.new(pem) 39 | cert = OpenSSL::X509::Certificate.new(pem) 40 | $openssl_self_signed_ctx = OpenSSL::SSL::SSLContext.new 41 | $openssl_self_signed_ctx.key = key 42 | $openssl_self_signed_ctx.cert = cert 43 | -------------------------------------------------------------------------------- /spec/server_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "capybara/webkit/server" 3 | 4 | describe Capybara::Webkit::Connection do 5 | it "ensures the process ends when the parent process ends", skip_on_windows: true, skip_on_jruby: true do 6 | sleep 1 # Without this sleep popen3 hangs on OSX when running the tests - not really sure why 7 | read_io, write_io = IO.pipe 8 | 9 | fork_pid = fork do 10 | read_io.close 11 | server = start_server 12 | write_io.write(server.pid) 13 | write_io.close 14 | Process.wait(server.pid) 15 | end 16 | 17 | write_io.close 18 | 19 | webkit_pid = read_io.read.to_i 20 | expect(webkit_pid).to be > 1 21 | read_io.close 22 | Process.kill(9, fork_pid) 23 | 24 | eventually do 25 | expect { Process.getpgid(webkit_pid) }.to raise_error Errno::ESRCH 26 | end 27 | end 28 | 29 | it "shouldn't output extraneous warnings when exiting", skip_on_windows: true do 30 | output_str, status = Open3.capture2e("rspec spec/fixtures/exit_text.rb") 31 | expect(status.exitstatus).to eq(0) 32 | expect(output_str).not_to include("AsynchronousCloseException") 33 | end 34 | 35 | it "raises an error if the server has stopped", skip_on_windows: true do 36 | path = "false" 37 | stub_const("Capybara::Webkit::Server::SERVER_PATH", path) 38 | 39 | expect { start_server }. 40 | to raise_error( 41 | Capybara::Webkit::ConnectionError, 42 | "#{path} failed to start.") 43 | end 44 | 45 | it "raises an error if the server is not ready", skip_on_windows: true do 46 | server_path = File.expand_path(File.join(__FILE__, "..", "fixtures", "fake_server.sh")) 47 | stub_const("Capybara::Webkit::Server::SERVER_PATH", server_path) 48 | start_timeout = 0.5 49 | stub_const( 50 | "Capybara::Webkit::Server::WEBKIT_SERVER_START_TIMEOUT", 51 | start_timeout, 52 | ) 53 | 54 | error_string = 55 | "#{server_path} failed to start after #{start_timeout} seconds." 56 | 57 | expect { start_server }. 58 | to raise_error(Capybara::Webkit::ConnectionError, error_string) 59 | end 60 | 61 | it "boots a server to talk to" do 62 | url = "http://#{@rack_server.host}:#{@rack_server.port}/" 63 | server = start_server 64 | socket = connect_to(server) 65 | socket.puts "Visit" 66 | socket.puts 1 67 | socket.puts url.to_s.bytesize 68 | socket.print url 69 | expect(socket.gets).to eq "ok\n" 70 | expect(socket.gets).to eq "0\n" 71 | socket.puts "Body" 72 | socket.puts 0 73 | expect(socket.gets).to eq "ok\n" 74 | response_length = socket.gets.to_i 75 | response = socket.read(response_length) 76 | expect(response).to include("Hey there") 77 | end 78 | 79 | it "forwards stderr to the given IO object" do 80 | read_io, write_io = IO.pipe 81 | server = start_server(stderr: write_io) 82 | socket = connect_to(server) 83 | socket.puts "EnableLogging" 84 | socket.puts 0 85 | 86 | script = "console.log('hello world')" 87 | socket.puts "Execute" 88 | socket.puts 1 89 | socket.puts script.to_s.bytesize 90 | socket.print script 91 | 92 | expect(read_io).to include_response /\n\d{2}:\d{2}:\d{2}\.\d{3} hello world/ 93 | end 94 | 95 | it "does not forward stderr to nil" do 96 | expect(IO).not_to receive(:copy_stream) 97 | start_server(stderr: nil) 98 | end 99 | 100 | it "prints a deprecation warning if the stdout option is used" do 101 | expect_any_instance_of(Capybara::Webkit::Server).to receive(:warn) 102 | start_server(stdout: nil) 103 | end 104 | 105 | it "does not forward stdout to nil if the stdout option is used" do 106 | allow_any_instance_of(Capybara::Webkit::Server).to receive(:warn) 107 | expect(IO).not_to receive(:copy_stream) 108 | start_server(stdout: nil) 109 | end 110 | 111 | it "returns the server port" do 112 | expect(start_server.port).to be_between 0x400, 0xffff 113 | end 114 | 115 | it "chooses a new port number for a new connection" do 116 | expect(start_server.port).not_to eq start_server.port 117 | end 118 | 119 | before(:all) do 120 | @app = lambda do |_| 121 | body = "Hey there" 122 | [ 123 | 200, 124 | { "Content-Type" => "text/html", "Content-Length" => body.size.to_s }, 125 | [body], 126 | ] 127 | end 128 | @rack_server = Capybara::Server.new(@app) 129 | @rack_server.boot 130 | end 131 | 132 | def start_server(options = {}) 133 | Capybara::Webkit::Server.new(options).tap(&:start) 134 | end 135 | 136 | def connect_to(server) 137 | TCPSocket.open("127.0.0.1", server.port) 138 | end 139 | 140 | def eventually 141 | polling_interval = 0.1 142 | time_limit = Time.now + 3 143 | loop do 144 | begin 145 | yield 146 | return 147 | rescue RSpec::Expectations::ExpectationNotMetError => error 148 | raise error if Time.now >= time_limit 149 | sleep polling_interval 150 | end 151 | end 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'rbconfig' 3 | require 'capybara' 4 | 5 | PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')).freeze 6 | 7 | $LOAD_PATH << File.join(PROJECT_ROOT, 'lib') 8 | 9 | Dir[File.join(PROJECT_ROOT, 'spec', 'support', '**', '*.rb')].each { |file| require(file) } 10 | 11 | require 'capybara/webkit' 12 | $webkit_server = Capybara::Webkit::Server.new 13 | $webkit_connection = Capybara::Webkit::Connection.new(server: $webkit_server) 14 | $webkit_browser = Capybara::Webkit::Browser.new($webkit_connection) 15 | 16 | if ENV["DEBUG"] 17 | $webkit_browser.enable_logging 18 | end 19 | 20 | require 'capybara/spec/spec_helper' 21 | 22 | Capybara.register_driver :reusable_webkit do |app| 23 | Capybara::Webkit::Driver.new(app, :browser => $webkit_browser) 24 | end 25 | 26 | def has_internet? 27 | require 'resolv' 28 | dns_resolver = Resolv::DNS.new 29 | begin 30 | dns_resolver.getaddress("example.com") 31 | true 32 | rescue Resolv::ResolvError 33 | false 34 | end 35 | end 36 | 37 | RSpec.configure do |c| 38 | Capybara::SpecHelper.configure(c) 39 | 40 | c.filter_run_excluding :skip_on_windows => !(RbConfig::CONFIG['host_os'] =~ /mingw32/).nil? 41 | c.filter_run_excluding :skip_on_jruby => !defined?(::JRUBY_VERSION).nil? 42 | c.filter_run_excluding :selenium_compatibility => (Capybara::VERSION =~ /^2\.4\./).nil? 43 | c.filter_run_excluding :skip_if_offline => !has_internet? 44 | 45 | #Check for QT version is 4 to skip QT5 required specs 46 | #This should be removed once support for QT4 is dropped 47 | require 'capybara_webkit_builder' 48 | c.filter_run_excluding :skip_on_qt4 => !(%x(#{CapybaraWebkitBuilder.qmake_bin} -v).match(/Using Qt version 4/)).nil? 49 | 50 | c.filter_run_excluding :full_description => lambda { |description, metadata| 51 | patterns = [ 52 | # Accessing unattached nodes is allowed when reload is disabled - Legacy behavior 53 | /^Capybara::Session webkit node #reload without automatic reload should not automatically reload/, 54 | # QtWebkit doesn't support datalist 55 | /^Capybara::Session webkit #select input with datalist should select an option/, 56 | # We focus the next window instead of failing when closing windows. 57 | /^Capybara::Session webkit Capybara::Window\s*#close.*no_such_window_error/, 58 | # QtWebkit doesn't support HTTP 308 response status 59 | /^Capybara::Session webkit #click_button should follow permanent redirects that maintain method/, 60 | # No support for fullscreen window 61 | /^Capybara::Session webkit Capybara::Window#fullscreen should be able to fullscreen the window/, 62 | ] 63 | # These tests were implemented in a non-compatible manner in Capybara < 2.12.0 (outerWidth, outerHeight) 64 | if Gem::Version.new(Capybara::VERSION) < Gem::Version.new("2.12.0") 65 | patterns << /Capybara::Session webkit Capybara::Window\s*#(size|resize_to|maximize)/ << 66 | /Capybara::Session webkit node\s*#set should allow me to change the contents of a contenteditable elements child/ 67 | end 68 | patterns.any? { |pattern| description =~ pattern } 69 | } 70 | 71 | c.filter_run :focus unless ENV["TRAVIS"] 72 | c.run_all_when_everything_filtered = true 73 | end 74 | 75 | def with_env_vars(vars) 76 | old_env_variables = {} 77 | vars.each do |key, value| 78 | old_env_variables[key] = ENV[key] 79 | ENV[key] = value 80 | end 81 | 82 | yield 83 | 84 | old_env_variables.each do |key, value| 85 | ENV[key] = value 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /spec/support/app_runner.rb: -------------------------------------------------------------------------------- 1 | # Boots a single Capybara::Server for a Rack application that delegates to another, singleton Rack 2 | # application that can be configured for each spec. 3 | 4 | require 'sinatra/base' 5 | 6 | module AppRunner 7 | class << self 8 | attr_accessor :app, :app_host, :browser, :configuration 9 | end 10 | 11 | def self.boot 12 | app_container = lambda { |env| AppRunner.app.call(env) } 13 | server = Capybara::Server.new(app_container) 14 | server.boot 15 | self.app_host = "http://127.0.0.1:#{server.port}" 16 | end 17 | 18 | def self.reset 19 | self.app = lambda do |env| 20 | [200, { 'Content-Type' => 'html', 'Content-Length' => 0 }, []] 21 | end 22 | 23 | self.browser = $webkit_browser 24 | self.browser.reset! 25 | 26 | self.configuration = Capybara::Webkit::Configuration.new 27 | end 28 | 29 | def run_application(app) 30 | AppRunner.app = app 31 | end 32 | 33 | def configure 34 | yield AppRunner.configuration 35 | end 36 | 37 | def fork_connection 38 | AppRunner.fork_connection 39 | end 40 | 41 | def self.fork_connection 42 | server = Capybara::Webkit::Server.new(options) 43 | connection = Capybara::Webkit::Connection.new(server: server) 44 | AppRunner.browser = Capybara::Webkit::Browser.new(connection) 45 | connection 46 | end 47 | 48 | def driver_for_app(&body) 49 | app = Class.new(ExampleApp, &body) 50 | run_application app 51 | AppRunner.build_driver 52 | end 53 | 54 | def driver_for_html(html) 55 | run_application_for_html html 56 | AppRunner.build_driver 57 | end 58 | 59 | def session_for_app(&body) 60 | app = Class.new(ExampleApp, &body) 61 | run_application app 62 | Capybara::Session.new(:reusable_webkit, AppRunner.app) 63 | end 64 | 65 | def run_application_for_html(html) 66 | run_application lambda { |env| 67 | [200, { 'Content-Type' => 'text/html', 'Content-Length' => html.size.to_s }, [html]] 68 | } 69 | end 70 | 71 | def self.build_driver 72 | Capybara::Webkit::Driver.new(app, options.merge(browser: browser)) 73 | end 74 | 75 | def self.options 76 | configuration.to_hash 77 | end 78 | private_class_method :options 79 | 80 | def self.included(example_group) 81 | example_group.class_eval do 82 | before { AppRunner.reset } 83 | after { $webkit_browser.reset! } 84 | end 85 | end 86 | end 87 | 88 | class ExampleApp < Sinatra::Base 89 | # Sinatra fixes invalid responses that would break QWebPage, so this middleware breaks them again 90 | # for testing purposes. 91 | class ResponseInvalidator 92 | def initialize(app) 93 | @app = app 94 | end 95 | 96 | def call(env) 97 | response = @app.call(env) 98 | if response.to_a[1]['X-Response-Invalid'] 99 | [404, {}, []] 100 | else 101 | response 102 | end 103 | end 104 | end 105 | 106 | use ResponseInvalidator 107 | 108 | def invalid_response 109 | [200, { 'X-Response-Invalid' => 'TRUE' }, []] 110 | end 111 | end 112 | 113 | AppRunner.boot 114 | -------------------------------------------------------------------------------- /spec/support/matchers/include_response.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :include_response do |expected_response| 2 | read_timeout = 2 3 | read_bytes = 4096 4 | response = "" 5 | 6 | match do |read_io| 7 | found_response = false 8 | 9 | while !found_response && IO.select([read_io], nil, nil, read_timeout) do 10 | response += read_io.read_nonblock(read_bytes) 11 | found_response = if expected_response.is_a? Regexp 12 | response.match(expected_response) 13 | else 14 | response.include?(expected_response) 15 | end 16 | end 17 | 18 | found_response 19 | end 20 | 21 | send(respond_to?(:failure_message) ? :failure_message : :failure_message_for_should) do |actual| 22 | "expected #{response} to include #{expected_response}" 23 | end 24 | 25 | send(respond_to?(:failure_message_when_negated) ? :failure_message_when_negated : :failure_message_for_should_not) do |actual| 26 | "expected #{response} to not include #{expected_response}" 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/support/output_writer.rb: -------------------------------------------------------------------------------- 1 | shared_examples_for "output writer" do 2 | before do 3 | @read, @write = IO.pipe 4 | configure { |config| config.stderr = @write } 5 | fork_connection 6 | end 7 | 8 | let(:stderr) do 9 | @write.close 10 | @read.read 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /src/AcceptAlert.cpp: -------------------------------------------------------------------------------- 1 | #include "AcceptAlert.h" 2 | #include "SocketCommand.h" 3 | #include "WebPage.h" 4 | #include "WebPageManager.h" 5 | 6 | AcceptAlert::AcceptAlert(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void AcceptAlert::start() { 10 | finish(true, page()->acceptAlert(arguments()[0])); 11 | } 12 | -------------------------------------------------------------------------------- /src/AcceptAlert.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class AcceptAlert : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | AcceptAlert(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/AllowUrl.cpp: -------------------------------------------------------------------------------- 1 | #include "AllowUrl.h" 2 | #include "SocketCommand.h" 3 | #include "WebPage.h" 4 | #include "WebPageManager.h" 5 | 6 | AllowUrl::AllowUrl( 7 | WebPageManager *manager, 8 | QStringList &arguments, 9 | QObject *parent 10 | ) : SocketCommand(manager, arguments, parent) { 11 | } 12 | 13 | void AllowUrl::start() { 14 | manager()->allowUrl(arguments()[0]); 15 | finish(true); 16 | } 17 | -------------------------------------------------------------------------------- /src/AllowUrl.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class AllowUrl : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | AllowUrl(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /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 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 15 | //Reset Authentication cache 16 | networkAccessManager->clearAccessCache(); 17 | #endif 18 | networkAccessManager->setUserName(username); 19 | networkAccessManager->setPassword(password); 20 | 21 | finish(true); 22 | } 23 | 24 | -------------------------------------------------------------------------------- /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/BlacklistedRequestHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "BlacklistedRequestHandler.h" 2 | #include "NetworkReplyProxy.h" 3 | #include "NoOpReply.h" 4 | 5 | BlacklistedRequestHandler::BlacklistedRequestHandler( 6 | RequestHandler *next, 7 | QObject *parent 8 | ) : RequestHandler(parent) { 9 | m_next = next; 10 | } 11 | 12 | QNetworkReply* BlacklistedRequestHandler::handleRequest( 13 | NetworkAccessManager *manager, 14 | QNetworkAccessManager::Operation operation, 15 | QNetworkRequest &request, 16 | QIODevice *outgoingData 17 | ) { 18 | if (this->isBlacklisted(request.url())) { 19 | return new NetworkReplyProxy(new NoOpReply(request), this); 20 | } else { 21 | return m_next->handleRequest(manager, operation, request, outgoingData); 22 | } 23 | } 24 | 25 | void BlacklistedRequestHandler::setUrlBlacklist(QStringList urlBlacklist) { 26 | m_urlBlacklist.clear(); 27 | 28 | QStringListIterator iter(urlBlacklist); 29 | while (iter.hasNext()) { 30 | m_urlBlacklist << iter.next(); 31 | } 32 | } 33 | 34 | bool BlacklistedRequestHandler::isBlacklisted(QUrl url) { 35 | QString urlString = url.toString(); 36 | QStringListIterator iter(m_urlBlacklist); 37 | 38 | while (iter.hasNext()) { 39 | QRegExp blacklisted = QRegExp(iter.next()); 40 | blacklisted.setPatternSyntax(QRegExp::Wildcard); 41 | 42 | if(urlString.contains(blacklisted)) { 43 | return true; 44 | } 45 | } 46 | 47 | return false; 48 | } 49 | 50 | void BlacklistedRequestHandler::blockUrl(const QString &url) { 51 | m_urlBlacklist.append(url); 52 | } 53 | 54 | void BlacklistedRequestHandler::reset() { 55 | m_urlBlacklist.clear(); 56 | } 57 | -------------------------------------------------------------------------------- /src/BlacklistedRequestHandler.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "RequestHandler.h" 4 | 5 | class BlacklistedRequestHandler : public RequestHandler { 6 | public: 7 | BlacklistedRequestHandler(RequestHandler *next, QObject *parent = 0); 8 | virtual QNetworkReply* handleRequest( 9 | NetworkAccessManager *, 10 | QNetworkAccessManager::Operation, 11 | QNetworkRequest &, 12 | QIODevice * 13 | ); 14 | void setUrlBlacklist(QStringList urlBlacklist); 15 | void blockUrl(const QString &); 16 | void reset(); 17 | 18 | private: 19 | RequestHandler *m_next; 20 | QStringList m_urlBlacklist; 21 | bool isBlacklisted(QUrl url); 22 | }; 23 | -------------------------------------------------------------------------------- /src/BlockUrl.cpp: -------------------------------------------------------------------------------- 1 | #include "BlockUrl.h" 2 | #include "SocketCommand.h" 3 | #include "WebPage.h" 4 | #include "WebPageManager.h" 5 | 6 | BlockUrl::BlockUrl( 7 | WebPageManager *manager, 8 | QStringList &arguments, 9 | QObject *parent 10 | ) : SocketCommand(manager, arguments, parent) { 11 | } 12 | 13 | void BlockUrl::start() { 14 | manager()->blockUrl(arguments()[0]); 15 | finish(true); 16 | } 17 | -------------------------------------------------------------------------------- /src/BlockUrl.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class BlockUrl : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | BlockUrl(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /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 "EvaluateAsync.h" 9 | #include "Execute.h" 10 | #include "FrameFocus.h" 11 | #include "Header.h" 12 | #include "Render.h" 13 | #include "Body.h" 14 | #include "Status.h" 15 | #include "Headers.h" 16 | #include "SetCookie.h" 17 | #include "ClearCookies.h" 18 | #include "GetCookies.h" 19 | #include "SetProxy.h" 20 | #include "ConsoleMessages.h" 21 | #include "CurrentUrl.h" 22 | #include "SetTimeout.h" 23 | #include "GetTimeout.h" 24 | #include "WindowResize.h" 25 | #include "IgnoreSslErrors.h" 26 | #include "SetSkipImageLoading.h" 27 | #include "WindowFocus.h" 28 | #include "GetWindowHandles.h" 29 | #include "GetWindowHandle.h" 30 | #include "WebPageManager.h" 31 | #include "Authenticate.h" 32 | #include "EnableLogging.h" 33 | #include "SetConfirmAction.h" 34 | #include "SetPromptAction.h" 35 | #include "SetPromptText.h" 36 | #include "ClearPromptText.h" 37 | #include "JavascriptAlertMessages.h" 38 | #include "JavascriptConfirmMessages.h" 39 | #include "JavascriptPromptMessages.h" 40 | #include "SetUrlBlacklist.h" 41 | #include "Version.h" 42 | #include "Title.h" 43 | #include "FindCss.h" 44 | #include "WindowClose.h" 45 | #include "WindowOpen.h" 46 | #include "WindowSize.h" 47 | #include "WindowMaximize.h" 48 | #include "GoBack.h" 49 | #include "GoForward.h" 50 | #include "Refresh.h" 51 | #include "AcceptAlert.h" 52 | #include "FindModal.h" 53 | #include "SetUnknownUrlMode.h" 54 | #include "AllowUrl.h" 55 | #include "BlockUrl.h" 56 | #include "FrameTitle.h" 57 | #include "FrameUrl.h" 58 | 59 | CommandFactory::CommandFactory(WebPageManager *manager, QObject *parent) : QObject(parent) { 60 | m_manager = manager; 61 | } 62 | 63 | Command *CommandFactory::createCommand(const char *name, QStringList &arguments) { 64 | #include "find_command.h" 65 | return new NullCommand(QString(name)); 66 | } 67 | -------------------------------------------------------------------------------- /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 | m_pendingCommand = NULL; 21 | connect(m_socket, SIGNAL(readyRead()), m_commandParser, SLOT(checkNext())); 22 | connect(m_commandParser, SIGNAL(commandReady(Command *)), this, SLOT(commandReady(Command *))); 23 | connect(m_manager, SIGNAL(pageFinished(bool)), this, SLOT(pendingLoadFinished(bool))); 24 | } 25 | 26 | void Connection::commandReady(Command *command) { 27 | m_manager->log() << "Received" << command->toString(); 28 | startCommand(command); 29 | } 30 | 31 | void Connection::startCommand(Command *command) { 32 | if (m_pendingCommand) { 33 | m_pendingCommand->deleteLater(); 34 | } 35 | if (m_pageSuccess) { 36 | m_pendingCommand = new TimeoutCommand(new PageLoadingCommand(command, m_manager, this), m_manager, this); 37 | connect(m_pendingCommand, SIGNAL(finished(Response *)), this, SLOT(finishCommand(Response *))); 38 | m_pendingCommand->start(); 39 | } else { 40 | writePageLoadFailure(); 41 | } 42 | } 43 | 44 | void Connection::pendingLoadFinished(bool success) { 45 | m_pageSuccess = m_pageSuccess && success; 46 | } 47 | 48 | void Connection::writePageLoadFailure() { 49 | m_pageSuccess = true; 50 | QString message = currentPage()->failureString(); 51 | Response response(false, new ErrorMessage(message)); 52 | writeResponse(&response); 53 | } 54 | 55 | void Connection::finishCommand(Response *response) { 56 | m_pageSuccess = true; 57 | writeResponse(response); 58 | sender()->deleteLater(); 59 | m_pendingCommand = NULL; 60 | } 61 | 62 | void Connection::writeResponse(Response *response) { 63 | if (response->isSuccess()) 64 | m_socket->write("ok\n"); 65 | else 66 | m_socket->write("failure\n"); 67 | 68 | m_manager->log() << "Wrote response" << response->isSuccess() << response->message(); 69 | 70 | QByteArray messageUtf8 = response->message(); 71 | QString messageLength = QString::number(messageUtf8.size()) + "\n"; 72 | m_socket->write(messageLength.toLatin1()); 73 | m_socket->write(messageUtf8); 74 | } 75 | 76 | WebPage *Connection::currentPage() { 77 | return m_manager->currentPage(); 78 | } 79 | -------------------------------------------------------------------------------- /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 | Command *m_pendingCommand; 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /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()->mainFrame()->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/CustomHeadersRequestHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "CustomHeadersRequestHandler.h" 2 | #include "NetworkReplyProxy.h" 3 | #include "NoOpReply.h" 4 | 5 | CustomHeadersRequestHandler::CustomHeadersRequestHandler( 6 | RequestHandler *next, 7 | QObject *parent 8 | ) : RequestHandler(parent) { 9 | m_next = next; 10 | } 11 | 12 | QNetworkReply* CustomHeadersRequestHandler::handleRequest( 13 | NetworkAccessManager *manager, 14 | QNetworkAccessManager::Operation operation, 15 | QNetworkRequest &request, 16 | QIODevice *outgoingData 17 | ) { 18 | Q_UNUSED(manager) 19 | Q_UNUSED(operation) 20 | Q_UNUSED(outgoingData) 21 | 22 | QHashIterator item(m_headers); 23 | while (item.hasNext()) { 24 | item.next(); 25 | request.setRawHeader(item.key().toLatin1(), item.value().toLatin1()); 26 | } 27 | 28 | return m_next->handleRequest(manager, operation, request, outgoingData); 29 | } 30 | 31 | void CustomHeadersRequestHandler::addHeader(QString key, QString value) { 32 | m_headers.insert(key, value); 33 | } 34 | 35 | void CustomHeadersRequestHandler::reset() { 36 | m_headers.clear(); 37 | } 38 | -------------------------------------------------------------------------------- /src/CustomHeadersRequestHandler.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "RequestHandler.h" 5 | 6 | class CustomHeadersRequestHandler : public RequestHandler { 7 | public: 8 | CustomHeadersRequestHandler(RequestHandler *next, QObject *parent = 0); 9 | virtual QNetworkReply* handleRequest( 10 | NetworkAccessManager *, 11 | QNetworkAccessManager::Operation, 12 | QNetworkRequest &, 13 | QIODevice * 14 | ); 15 | void addHeader(QString, QString); 16 | virtual void reset(); 17 | 18 | private: 19 | RequestHandler *m_next; 20 | QHash m_headers; 21 | }; 22 | -------------------------------------------------------------------------------- /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 | QString script = arguments()[0]; 12 | QString jsonArgs; 13 | if (arguments().length()>1){ 14 | jsonArgs = arguments()[1]; 15 | } else { 16 | jsonArgs ="[]"; 17 | } 18 | QString eval_script = QString("(function(){" 19 | " for(var i=0; icurrentFrame()->addToJavaScriptWindowObject("CapybaraInvocation", &invocation_stub); 32 | QVariant result = page()->currentFrame()->evaluateJavaScript(eval_script); 33 | JsonSerializer serializer; 34 | finish(true, serializer.serialize(result)); 35 | } 36 | -------------------------------------------------------------------------------- /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/EvaluateAsync.cpp: -------------------------------------------------------------------------------- 1 | #include "EvaluateAsync.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "JsonSerializer.h" 5 | #include 6 | 7 | EvaluateAsync::EvaluateAsync(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 8 | } 9 | 10 | void EvaluateAsync::start() { 11 | QString script = arguments()[0]; 12 | QString jsonArgs; 13 | if (arguments().length()>1){ 14 | jsonArgs = arguments()[1]; 15 | } else { 16 | jsonArgs ="[]"; 17 | } 18 | QString eval_script = QString("(function(){" 19 | " for(var i=0; icurrentFrame()->addToJavaScriptWindowObject("CapybaraInvocation", &invocation_stub); 40 | page()->currentFrame()->addToJavaScriptWindowObject("CapybaraAsync", this); 41 | page()->currentFrame()->evaluateJavaScript(eval_script); 42 | } 43 | 44 | void EvaluateAsync::asyncResult(QVariantList result) { 45 | JsonSerializer serializer; 46 | finish(true, serializer.serialize(result)); 47 | } 48 | 49 | void EvaluateAsync::asyncResult(QVariant result) { 50 | JsonSerializer serializer; 51 | finish(true, serializer.serialize(result)); 52 | } 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/EvaluateAsync.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | #include 4 | 5 | class EvaluateAsync : public SocketCommand { 6 | Q_OBJECT 7 | 8 | public: 9 | EvaluateAsync(WebPageManager *, QStringList &arguments, QObject *parent = 0); 10 | virtual void start(); 11 | Q_INVOKABLE void asyncResult(QVariant result); 12 | Q_INVOKABLE void asyncResult(QVariantList result); 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /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 jsonArgs; 11 | if (arguments().length()>1){ 12 | jsonArgs = arguments()[1]; 13 | } else { 14 | jsonArgs ="[]"; 15 | } 16 | QString script = QString("(function(){" 17 | " for(var i=0; icurrentFrame()->addToJavaScriptWindowObject("CapybaraInvocation", &invocation_stub); 29 | QVariant result = page()->currentFrame()->evaluateJavaScript(script); 30 | if (result.isValid()) { 31 | finish(true); 32 | } else { 33 | finish(false, new ErrorMessage("Javascript failed to execute")); 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /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/FindModal.cpp: -------------------------------------------------------------------------------- 1 | #include "FindModal.h" 2 | #include "SocketCommand.h" 3 | #include "WebPage.h" 4 | #include "WebPageManager.h" 5 | #include "ErrorMessage.h" 6 | 7 | FindModal::FindModal(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 8 | } 9 | 10 | void FindModal::start() { 11 | if (page()->modalCount() == 0) { 12 | connect(page(), SIGNAL(modalReady()), SLOT(handleModalReady())); 13 | } else { 14 | handleModalReady(); 15 | } 16 | } 17 | 18 | void FindModal::handleModalReady() { 19 | sender()->disconnect(SIGNAL(modalReady()), this, SLOT(handleModalReady())); 20 | QString message = page()->modalMessage(); 21 | if (message.isNull()) { 22 | finish(false, new ErrorMessage("ModalNotFound", "")); 23 | } else { 24 | finish(true, message); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/FindModal.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class FindModal : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | FindModal(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | 10 | public slots: 11 | void handleModalReady(); 12 | }; 13 | 14 | -------------------------------------------------------------------------------- /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 | switch(arguments().length()) { 12 | case 1: 13 | focusId(arguments()[0]); 14 | break; 15 | case 2: 16 | focusIndex(arguments()[1].toInt()); 17 | break; 18 | default: 19 | focusParent(); 20 | } 21 | } 22 | 23 | void FrameFocus::findFrames() { 24 | frames = page()->currentFrame()->childFrames(); 25 | } 26 | 27 | void FrameFocus::focusIndex(int index) { 28 | findFrames(); 29 | if (isFrameAtIndex(index)) { 30 | frames[index]->setFocus(); 31 | page()->setCurrentFrameParent(frames[index]->parentFrame()); 32 | success(); 33 | } else { 34 | frameNotFound(); 35 | } 36 | } 37 | 38 | bool FrameFocus::isFrameAtIndex(int index) { 39 | return 0 <= index && index < frames.length(); 40 | } 41 | 42 | void FrameFocus::focusId(QString name) { 43 | findFrames(); 44 | for (int i = 0; i < frames.length(); i++) { 45 | if (frames[i]->frameName().compare(name) == 0) { 46 | frames[i]->setFocus(); 47 | page()->setCurrentFrameParent(frames[i]->parentFrame()); 48 | success(); 49 | return; 50 | } 51 | } 52 | 53 | frameNotFound(); 54 | } 55 | 56 | void FrameFocus::focusParent() { 57 | // if (page()->currentFrame()->parentFrame() == 0) { 58 | if (page()->currentFrameParent() == 0) { 59 | finish(false, new ErrorMessage("Already at parent frame.")); 60 | } else { 61 | // page()->currentFrame()->parentFrame()->setFocus(); 62 | page()->currentFrameParent()->setFocus(); 63 | page()->setCurrentFrameParent(page()->currentFrameParent()->parentFrame()); 64 | success(); 65 | } 66 | } 67 | 68 | void FrameFocus::frameNotFound() { 69 | finish(false, new ErrorMessage("Unable to locate frame.")); 70 | } 71 | 72 | void FrameFocus::success() { 73 | finish(true); 74 | } 75 | -------------------------------------------------------------------------------- /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/FrameTitle.cpp: -------------------------------------------------------------------------------- 1 | #include "FrameTitle.h" 2 | #include "SocketCommand.h" 3 | #include "WebPage.h" 4 | #include "WebPageManager.h" 5 | 6 | FrameTitle::FrameTitle(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void FrameTitle::start() { 10 | finish(true, page()->currentFrame()->title()); 11 | } 12 | -------------------------------------------------------------------------------- /src/FrameTitle.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class FrameTitle : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | FrameTitle(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/FrameUrl.cpp: -------------------------------------------------------------------------------- 1 | #include "FrameUrl.h" 2 | #include "SocketCommand.h" 3 | #include "WebPage.h" 4 | #include "WebPageManager.h" 5 | 6 | FrameUrl::FrameUrl(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void FrameUrl::start() { 10 | QVariant result = page()->currentFrame()->evaluateJavaScript("window.location.toString()"); 11 | QString url = result.toString(); 12 | finish(true, url); 13 | } 14 | -------------------------------------------------------------------------------- /src/FrameUrl.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class FrameUrl : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | FrameUrl(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /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/GoBack.cpp: -------------------------------------------------------------------------------- 1 | #include "GoBack.h" 2 | #include "SocketCommand.h" 3 | #include "WebPage.h" 4 | #include "WebPageManager.h" 5 | 6 | GoBack::GoBack(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void GoBack::start() { 10 | page()->triggerAction(QWebPage::Back); 11 | finish(true); 12 | } 13 | -------------------------------------------------------------------------------- /src/GoBack.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class GoBack : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | GoBack(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/GoForward.cpp: -------------------------------------------------------------------------------- 1 | #include "GoForward.h" 2 | #include "SocketCommand.h" 3 | #include "WebPage.h" 4 | #include "WebPageManager.h" 5 | 6 | GoForward::GoForward(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void GoForward::start() { 10 | page()->triggerAction(QWebPage::Forward); 11 | finish(true); 12 | } 13 | -------------------------------------------------------------------------------- /src/GoForward.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class GoForward : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | GoForward(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 | if (key.toLower().replace("-", "_") == "user_agent") { 13 | page()->setUserAgent(value); 14 | } else { 15 | manager()->addHeader(key, value); 16 | } 17 | finish(true); 18 | } 19 | -------------------------------------------------------------------------------- /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 | #include "JsonSerializer.h" 5 | 6 | Headers::Headers(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void Headers::start() { 10 | JsonSerializer serializer; 11 | QByteArray json = serializer.serialize(page()->pageHeaders()); 12 | finish(true, json); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /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.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 | Q_PROPERTY(Qt::Key key_enum) 17 | 18 | public: 19 | JavascriptInvocation(const QString &functionName, bool allowUnattached, const QStringList &arguments, WebPage *page, QObject *parent = 0); 20 | QString &functionName(); 21 | bool allowUnattached(); 22 | QStringList &arguments(); 23 | Q_INVOKABLE void leftClick(int x, int y, QVariantList keys); 24 | Q_INVOKABLE void rightClick(int x, int y, QVariantList keys); 25 | Q_INVOKABLE void doubleClick(int x, int y, QVariantList keys); 26 | Q_INVOKABLE bool clickTest(QWebElement element, int absoluteX, int absoluteY); 27 | Q_INVOKABLE QVariantMap clickPosition(QWebElement element, int left, int top, int width, int height); 28 | Q_INVOKABLE void hover(int absoluteX, int absoluteY); 29 | Q_INVOKABLE void keypress(QChar); 30 | Q_INVOKABLE void namedKeydown(QString keyName); 31 | Q_INVOKABLE void namedKeyup(QString keyName); 32 | Q_INVOKABLE void namedKeypress(QString keyName, QString modifiers); 33 | Q_INVOKABLE const QString render(void); 34 | QVariant getError(); 35 | void setError(QVariant error); 36 | InvocationResult invoke(QWebFrame *); 37 | 38 | private: 39 | QString m_functionName; 40 | bool m_allowUnattached; 41 | QStringList m_arguments; 42 | WebPage *m_page; 43 | QVariant m_error; 44 | void hover(const QPoint &); 45 | int keyCodeFor(const QChar &); 46 | int keyCodeForName(const QString &); 47 | Qt::Key key_enum; 48 | Qt::KeyboardModifiers m_currentModifiers; 49 | Qt::KeyboardModifiers modifiers(const QVariantList& keys); 50 | static QMap makeModifiersMap(); 51 | static QMap m_modifiersMap; 52 | }; 53 | 54 | -------------------------------------------------------------------------------- /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 | case QMetaType::QDateTime: 17 | { 18 | addString(object.toString()); 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/MissingContentHeaderRequestHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "MissingContentHeaderRequestHandler.h" 2 | #include "NetworkReplyProxy.h" 3 | #include "NoOpReply.h" 4 | 5 | MissingContentHeaderRequestHandler::MissingContentHeaderRequestHandler( 6 | RequestHandler *next, 7 | QObject *parent 8 | ) : RequestHandler(parent) { 9 | m_next = next; 10 | } 11 | 12 | QNetworkReply* MissingContentHeaderRequestHandler::handleRequest( 13 | NetworkAccessManager *manager, 14 | QNetworkAccessManager::Operation operation, 15 | QNetworkRequest &request, 16 | QIODevice *outgoingData 17 | ) { 18 | if (operation != QNetworkAccessManager::PostOperation && operation != QNetworkAccessManager::PutOperation) { 19 | request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant()); 20 | } 21 | 22 | return m_next->handleRequest(manager, operation, request, outgoingData); 23 | } 24 | -------------------------------------------------------------------------------- /src/MissingContentHeaderRequestHandler.h: -------------------------------------------------------------------------------- 1 | #include "RequestHandler.h" 2 | 3 | class MissingContentHeaderRequestHandler : public RequestHandler { 4 | public: 5 | MissingContentHeaderRequestHandler(RequestHandler *next, QObject *parent = 0); 6 | virtual QNetworkReply* handleRequest( 7 | NetworkAccessManager *, 8 | QNetworkAccessManager::Operation, 9 | QNetworkRequest &, 10 | QIODevice * 11 | ); 12 | 13 | private: 14 | RequestHandler *m_next; 15 | }; 16 | -------------------------------------------------------------------------------- /src/NetworkAccessManager.cpp: -------------------------------------------------------------------------------- 1 | #include "NetworkAccessManager.h" 2 | #include "WebPage.h" 3 | #include "NetworkReplyProxy.h" 4 | #include "RequestHandler.h" 5 | 6 | NetworkAccessManager::NetworkAccessManager( 7 | RequestHandler * requestHandler, 8 | QObject *parent 9 | ) : QNetworkAccessManager(parent) { 10 | m_requestHandler = requestHandler; 11 | connect(this, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), SLOT(provideAuthentication(QNetworkReply*,QAuthenticator*))); 12 | connect(this, SIGNAL(finished(QNetworkReply *)), this, SLOT(finished(QNetworkReply *))); 13 | disableKeyChainLookup(); 14 | } 15 | 16 | QNetworkReply* NetworkAccessManager::sendRequest( 17 | QNetworkAccessManager::Operation operation, 18 | const QNetworkRequest &request, 19 | QIODevice * outgoingData 20 | ) { 21 | QNetworkReply *reply = new NetworkReplyProxy( 22 | QNetworkAccessManager::createRequest(operation, 23 | request, 24 | outgoingData 25 | ), 26 | this 27 | ); 28 | 29 | QByteArray url = reply->request().url().toEncoded(); 30 | emit requestCreated(url, reply); 31 | 32 | return reply; 33 | } 34 | 35 | QNetworkReply* NetworkAccessManager::createRequest( 36 | QNetworkAccessManager::Operation operation, 37 | const QNetworkRequest &unsafeRequest, 38 | QIODevice * outgoingData = 0 39 | ) { 40 | QNetworkRequest request(unsafeRequest); 41 | QNetworkReply *reply = 42 | m_requestHandler->handleRequest(this, operation, request, outgoingData); 43 | return reply; 44 | }; 45 | 46 | void NetworkAccessManager::finished(QNetworkReply *reply) { 47 | QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); 48 | if (redirectUrl.isValid()) 49 | m_redirectMappings[reply->url().resolved(redirectUrl)] = reply->url(); 50 | else { 51 | QUrl requestedUrl = reply->url(); 52 | while (m_redirectMappings.contains(requestedUrl)) 53 | requestedUrl = m_redirectMappings.take(requestedUrl); 54 | emit finished(requestedUrl, reply); 55 | } 56 | } 57 | 58 | void NetworkAccessManager::reset() { 59 | m_userName = QString(); 60 | m_password = QString(); 61 | } 62 | 63 | void NetworkAccessManager::setUserName(const QString &userName) { 64 | m_userName = userName; 65 | } 66 | 67 | void NetworkAccessManager::setPassword(const QString &password) { 68 | m_password = password; 69 | } 70 | 71 | void NetworkAccessManager::provideAuthentication(QNetworkReply *reply, QAuthenticator *authenticator) { 72 | Q_UNUSED(reply); 73 | if (m_userName != authenticator->user()) 74 | authenticator->setUser(m_userName); 75 | if (m_password != authenticator->password()) 76 | authenticator->setPassword(m_password); 77 | } 78 | 79 | /* 80 | * This is a workaround for a Qt 5/OS X bug: 81 | * https://bugreports.qt-project.org/browse/QTBUG-30434 82 | */ 83 | void NetworkAccessManager::disableKeyChainLookup() { 84 | QNetworkProxy fixedProxy = proxy(); 85 | fixedProxy.setHostName(" "); 86 | setProxy(fixedProxy); 87 | } 88 | -------------------------------------------------------------------------------- /src/NetworkAccessManager.h: -------------------------------------------------------------------------------- 1 | #ifndef __NETWORKACCESSMANAGER_H 2 | #define __NETWORKACCESSMANAGER_H 3 | #include 4 | #include 5 | #include 6 | 7 | class RequestHandler; 8 | 9 | class NetworkAccessManager : public QNetworkAccessManager { 10 | Q_OBJECT 11 | 12 | public: 13 | NetworkAccessManager(RequestHandler *, QObject *parent = 0); 14 | void reset(); 15 | void setUserName(const QString &userName); 16 | void setPassword(const QString &password); 17 | QNetworkReply* sendRequest( 18 | QNetworkAccessManager::Operation, 19 | const QNetworkRequest &, 20 | QIODevice * 21 | ); 22 | 23 | protected: 24 | QNetworkReply* createRequest( 25 | QNetworkAccessManager::Operation, 26 | const QNetworkRequest &, 27 | QIODevice * 28 | ); 29 | QString m_userName; 30 | QString m_password; 31 | 32 | private: 33 | void disableKeyChainLookup(); 34 | 35 | QHash m_redirectMappings; 36 | RequestHandler * m_requestHandler; 37 | 38 | private slots: 39 | void provideAuthentication(QNetworkReply *reply, QAuthenticator *authenticator); 40 | void finished(QNetworkReply *); 41 | 42 | signals: 43 | void requestCreated(QByteArray &url, QNetworkReply *reply); 44 | void finished(QUrl &, QNetworkReply *); 45 | }; 46 | #endif 47 | -------------------------------------------------------------------------------- /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/NetworkRequestFactory.cpp: -------------------------------------------------------------------------------- 1 | #include "NetworkRequestFactory.h" 2 | #include "NetworkAccessManager.h" 3 | 4 | NetworkRequestFactory::NetworkRequestFactory(QObject *parent) : 5 | RequestHandler(parent) { 6 | } 7 | 8 | QNetworkReply* NetworkRequestFactory::handleRequest( 9 | NetworkAccessManager *manager, 10 | QNetworkAccessManager::Operation operation, 11 | QNetworkRequest &request, 12 | QIODevice *outgoingData 13 | ) { 14 | return manager->sendRequest(operation, request, outgoingData); 15 | } 16 | -------------------------------------------------------------------------------- /src/NetworkRequestFactory.h: -------------------------------------------------------------------------------- 1 | #include "RequestHandler.h" 2 | 3 | class NetworkRequestFactory : public RequestHandler { 4 | public: 5 | NetworkRequestFactory(QObject *parent = 0); 6 | virtual QNetworkReply* handleRequest( 7 | NetworkAccessManager *, 8 | QNetworkAccessManager::Operation, 9 | QNetworkRequest &, 10 | QIODevice * 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /src/NoOpReply.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "NoOpReply.h" 3 | 4 | NoOpReply::NoOpReply(const 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(const 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 "JsonSerializer.h" 5 | #include "InvocationResult.h" 6 | 7 | Node::Node(WebPageManager *manager, QStringList &arguments, QObject *parent) : JavascriptCommand(manager, arguments, parent) { 8 | } 9 | 10 | void Node::start() { 11 | QStringList functionArguments(arguments()); 12 | QString functionName = functionArguments.takeFirst(); 13 | QString allowUnattached = functionArguments.takeFirst(); 14 | InvocationResult result = page()->invokeCapybaraFunction(functionName, allowUnattached == "true", functionArguments); 15 | if (functionName == "focus_frame") { 16 | page()->setCurrentFrameParent(page()->currentFrame()->parentFrame()); 17 | } 18 | 19 | if (result.hasError()) { 20 | finish(&result); 21 | } else { 22 | JsonSerializer serializer; 23 | InvocationResult jsonResult = InvocationResult(serializer.serialize(result.result())); 24 | finish(&jsonResult); 25 | } 26 | } 27 | 28 | QString Node::toString() const { 29 | QStringList functionArguments(arguments()); 30 | return QString("Node.") + functionArguments.takeFirst(); 31 | } 32 | -------------------------------------------------------------------------------- /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->log() << "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->log() << "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->log() << 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->log() << "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/Refresh.cpp: -------------------------------------------------------------------------------- 1 | #include "Refresh.h" 2 | #include "SocketCommand.h" 3 | #include "WebPage.h" 4 | #include "WebPageManager.h" 5 | 6 | Refresh::Refresh(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void Refresh::start() { 10 | page()->triggerAction(QWebPage::Reload); 11 | finish(true); 12 | } 13 | -------------------------------------------------------------------------------- /src/Refresh.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class Refresh : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | Refresh(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /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/RequestHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "RequestHandler.h" 2 | 3 | RequestHandler::RequestHandler(QObject *parent) : QObject(parent) { 4 | } 5 | -------------------------------------------------------------------------------- /src/RequestHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef __REQUESTHANDLER_H 2 | #define __REQUESTHANDLER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class NetworkAccessManager; 10 | 11 | class RequestHandler : public QObject { 12 | Q_OBJECT 13 | 14 | public: 15 | RequestHandler(QObject *parent = 0); 16 | 17 | virtual QNetworkReply* handleRequest( 18 | NetworkAccessManager *, 19 | QNetworkAccessManager::Operation, 20 | QNetworkRequest &, 21 | QIODevice * 22 | ) = 0; 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /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 | manager()->reset(); 10 | 11 | finish(true); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /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/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 | #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) 13 | QTextStream(stderr) << 14 | "WARNING: The next major version of capybara-webkit " << 15 | "will require at least version 5.0 of Qt. " << 16 | "You're using version " << QT_VERSION_STR << "." << endl; 17 | #endif 18 | 19 | connect(m_tcp_server, SIGNAL(newConnection()), this, SLOT(handleConnection())); 20 | return m_tcp_server->listen(QHostAddress::LocalHost, 0); 21 | } 22 | 23 | quint16 Server::server_port() const { 24 | return m_tcp_server->serverPort(); 25 | } 26 | 27 | void Server::handleConnection() { 28 | QTcpSocket *socket = m_tcp_server->nextPendingConnection(); 29 | new Connection(socket, new WebPageManager(this), this); 30 | } 31 | -------------------------------------------------------------------------------- /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/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 | QString index; 10 | switch (arguments().length()) { 11 | case 2: 12 | index = page()->setConfirmAction(arguments()[0], arguments()[1]); 13 | break; 14 | default: 15 | page()->setConfirmAction(arguments()[0]); 16 | } 17 | 18 | finish(true, index); 19 | } 20 | -------------------------------------------------------------------------------- /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/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 | QString index; 10 | switch (arguments().length()) { 11 | case 3: 12 | index = page()->setPromptAction(arguments()[0], arguments()[1], arguments()[2]); 13 | break; 14 | case 2: 15 | index = page()->setPromptAction(arguments()[0], arguments()[1]); 16 | break; 17 | default: 18 | page()->setPromptAction(arguments()[0]); 19 | } 20 | 21 | finish(true, index); 22 | } 23 | -------------------------------------------------------------------------------- /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/SetUnknownUrlMode.cpp: -------------------------------------------------------------------------------- 1 | #include "SetUnknownUrlMode.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "ErrorMessage.h" 5 | 6 | SetUnknownUrlMode::SetUnknownUrlMode(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void SetUnknownUrlMode::start() { 10 | QString modeString = arguments()[0]; 11 | QStringList modes; 12 | modes << "warn" << "block"; 13 | 14 | switch(modes.indexOf(modeString)) { 15 | case 0: 16 | manager()->setUnknownUrlMode(UnknownUrlHandler::WARN); 17 | finish(true); 18 | break; 19 | case 1: 20 | manager()->setUnknownUrlMode(UnknownUrlHandler::BLOCK); 21 | finish(true); 22 | break; 23 | default: 24 | QString error = QString("Invalid mode string:") + modeString; 25 | finish(false, new ErrorMessage(error)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/SetUnknownUrlMode.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | #include "UnknownUrlHandler.h" 3 | 4 | class SetUnknownUrlMode : public SocketCommand { 5 | Q_OBJECT 6 | 7 | public: 8 | SetUnknownUrlMode(WebPageManager *, QStringList &arguments, QObject *parent = 0); 9 | virtual void start(); 10 | }; 11 | -------------------------------------------------------------------------------- /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 | manager()->setUrlBlacklist(arguments()); 12 | finish(true); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/SetUrlBlacklist.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class SetUrlBlacklist : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | SetUrlBlacklist(WebPageManager *manager, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | -------------------------------------------------------------------------------- /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 | QString SocketCommand::toString() const { 23 | QString result; 24 | QTextStream(&result) << metaObject()->className() << QString("(") << m_arguments.join(", ") << QString(")"); 25 | return result; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /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 | virtual QString toString() const; 18 | 19 | protected: 20 | WebPage *page() const; 21 | const QStringList &arguments() const; 22 | WebPageManager *manager() const; 23 | 24 | private: 25 | QStringList m_arguments; 26 | WebPageManager *m_manager; 27 | 28 | }; 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /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/StdinNotifier.cpp: -------------------------------------------------------------------------------- 1 | #include "StdinNotifier.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | StdinNotifier::StdinNotifier(QObject *parent) : QObject(parent) { 8 | m_notifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read, this); 9 | connect(m_notifier, SIGNAL(activated(int)), this, SLOT(notifierActivated())); 10 | } 11 | 12 | void StdinNotifier::notifierActivated() { 13 | std::string line; 14 | std::getline(std::cin, line); 15 | if (std::cin.eof()) { 16 | emit eof(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/StdinNotifier.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class QSocketNotifier; 4 | 5 | class StdinNotifier : public QObject { 6 | Q_OBJECT 7 | 8 | public: 9 | StdinNotifier(QObject *parent = 0); 10 | 11 | public slots: 12 | void notifierActivated(); 13 | 14 | signals: 15 | void eof(); 16 | 17 | private: 18 | QSocketNotifier *m_notifier; 19 | }; 20 | 21 | -------------------------------------------------------------------------------- /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->log() << 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()->mainFrame()->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/UnknownUrlHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "UnknownUrlHandler.h" 2 | #include "NetworkReplyProxy.h" 3 | #include "NoOpReply.h" 4 | 5 | UnknownUrlHandler::UnknownUrlHandler( 6 | RequestHandler *next, 7 | QObject *parent 8 | ) : RequestHandler(parent) { 9 | m_next = next; 10 | allowDefaultUrls(); 11 | m_mode = WARN; 12 | } 13 | 14 | QNetworkReply* UnknownUrlHandler::handleRequest( 15 | NetworkAccessManager *manager, 16 | QNetworkAccessManager::Operation operation, 17 | QNetworkRequest &request, 18 | QIODevice *outgoingData 19 | ) { 20 | QUrl url(request.url()); 21 | if (this->isUnknown(url)) { 22 | switch(m_mode) { 23 | case WARN: 24 | QTextStream(stderr) << 25 | "Request to unknown URL: " << url.toString() << endl << 26 | "To block requests to unknown URLs:" << endl << 27 | " Capybara::Webkit.configure do |config|" << endl << 28 | " config.block_unknown_urls" << endl << 29 | " end" << endl << 30 | "To allow just this URL:" << endl << 31 | " Capybara::Webkit.configure do |config|" << endl << 32 | " config.allow_url(\"" << url.toString() << "\")" << endl << 33 | " end" << endl << 34 | "To allow requests to URLs from this host:" << endl << 35 | " Capybara::Webkit.configure do |config|" << endl << 36 | " config.allow_url(\"" << url.host() << "\")" << endl << 37 | " end" << endl; 38 | break; 39 | case BLOCK: 40 | return new NetworkReplyProxy(new NoOpReply(request), this); 41 | } 42 | } 43 | 44 | return m_next->handleRequest(manager, operation, request, outgoingData); 45 | } 46 | 47 | void UnknownUrlHandler::allowUrl(const QString &url) { 48 | m_allowedUrls << url; 49 | } 50 | 51 | void UnknownUrlHandler::setMode(Mode mode) { 52 | m_mode = mode; 53 | } 54 | 55 | bool UnknownUrlHandler::isUnknown(QUrl url) { 56 | QStringListIterator iterator(m_allowedUrls); 57 | QString urlString = url.toString(); 58 | 59 | while (iterator.hasNext()) { 60 | QRegExp allowedUrl = QRegExp(iterator.next()); 61 | allowedUrl.setPatternSyntax(QRegExp::Wildcard); 62 | 63 | if(urlString.contains(allowedUrl)) { 64 | return false; 65 | } 66 | } 67 | 68 | return true; 69 | } 70 | 71 | void UnknownUrlHandler::reset() { 72 | m_allowedUrls.clear(); 73 | allowDefaultUrls(); 74 | } 75 | 76 | void UnknownUrlHandler::allowDefaultUrls() { 77 | m_allowedUrls.append(QString("127.0.0.1")); 78 | m_allowedUrls.append(QString("localhost")); 79 | m_allowedUrls.append(QString("data:*,*")); 80 | } 81 | -------------------------------------------------------------------------------- /src/UnknownUrlHandler.h: -------------------------------------------------------------------------------- 1 | #ifndef _REQUESTHANDLER_H 2 | #define _REQUESTHANDLER_H 3 | 4 | #include "RequestHandler.h" 5 | 6 | class UnknownUrlHandler : public RequestHandler { 7 | public: 8 | enum Mode { WARN, BLOCK }; 9 | 10 | UnknownUrlHandler(RequestHandler *next, QObject *parent = 0); 11 | virtual QNetworkReply* handleRequest( 12 | NetworkAccessManager *, 13 | QNetworkAccessManager::Operation, 14 | QNetworkRequest &, 15 | QIODevice * 16 | ); 17 | void allowUrl(const QString &); 18 | void setMode(Mode); 19 | void reset(); 20 | 21 | private: 22 | QStringList m_allowedUrls; 23 | bool isUnknown(QUrl); 24 | Mode m_mode; 25 | RequestHandler *m_next; 26 | void allowDefaultUrls(); 27 | }; 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /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 | #ifndef QTWEBKIT_VERSION_STR 4 | #include "qtwebkitversion.h" 5 | #endif 6 | 7 | class Version : public SocketCommand { 8 | Q_OBJECT 9 | 10 | public: 11 | Version(WebPageManager *, QStringList &arguments, QObject *parent = 0); 12 | virtual void start(); 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /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.h: -------------------------------------------------------------------------------- 1 | #ifndef _WEBPAGE_H 2 | #define _WEBPAGE_H 3 | #include 4 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 5 | #include 6 | #else 7 | #include 8 | #endif 9 | #include 10 | 11 | class WebPageManager; 12 | class InvocationResult; 13 | class NetworkReplyProxy; 14 | class QWebView; 15 | 16 | class WebPage : public QWebPage { 17 | Q_OBJECT 18 | 19 | public: 20 | WebPage(WebPageManager *, QObject *parent = 0); 21 | InvocationResult invokeCapybaraFunction(const char *name, bool allowUnattached, const QStringList &arguments); 22 | InvocationResult invokeCapybaraFunction(QString &name, bool allowUnattached, const QStringList &arguments); 23 | QString failureString(); 24 | QString userAgentForUrl(const QUrl &url ) const; 25 | void setUserAgent(QString userAgent); 26 | void setConfirmAction(QString action); 27 | QString setConfirmAction(QString action, QString message); 28 | QString setPromptAction(QString action, QString message, QString response); 29 | QString setPromptAction(QString action, QString message); 30 | void setPromptAction(QString action); 31 | void setPromptText(QString action); 32 | QString acceptAlert(QString); 33 | int getLastStatus(); 34 | void setCustomNetworkAccessManager(); 35 | bool render(const QString &fileName, const QSize &minimumSize); 36 | virtual bool extension (Extension extension, const ExtensionOption *option=0, ExtensionReturn *output=0); 37 | void setSkipImageLoading(bool skip); 38 | QVariantList consoleMessages(); 39 | QVariantList alertMessages(); 40 | QVariantList confirmMessages(); 41 | QVariantList promptMessages(); 42 | void createWindow(); 43 | void resetLocalStorage(); 44 | QWebPage *createWindow(WebWindowType type); 45 | QString uuid(); 46 | QString getWindowName(); 47 | bool matchesWindowSelector(QString); 48 | void setFocus(); 49 | void unsupportedContentFinishedReply(QNetworkReply *reply); 50 | QVariantMap pageHeaders(); 51 | QByteArray body(); 52 | QString contentType(); 53 | void mouseEvent(QEvent::Type type, const QPoint &position, Qt::MouseButton button, Qt::KeyboardModifiers modifiers = Qt::NoModifier); 54 | bool clickTest(QWebElement element, int absoluteX, int absoluteY); 55 | void resize(int, int); 56 | int modalCount(); 57 | QString modalMessage(); 58 | void setCurrentFrameParent(QWebFrame* frame); 59 | QWebFrame* currentFrameParent(); 60 | 61 | public slots: 62 | bool shouldInterruptJavaScript(); 63 | void injectJavascriptHelpers(); 64 | void loadStarted(); 65 | void loadFinished(bool); 66 | bool isLoading() const; 67 | void frameCreated(QWebFrame *); 68 | void handleSslErrorsForReply(QNetworkReply *reply, const QList &); 69 | void handleUnsupportedContent(QNetworkReply *reply); 70 | void replyFinished(QUrl &, QNetworkReply *); 71 | void remove(); 72 | 73 | signals: 74 | void pageFinished(bool); 75 | void requestCreated(QByteArray &url, QNetworkReply *reply); 76 | void replyFinished(QNetworkReply *reply); 77 | void modalReady(); 78 | 79 | protected: 80 | virtual void javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID); 81 | virtual void javaScriptAlert(QWebFrame *frame, const QString &message); 82 | virtual bool javaScriptConfirm(QWebFrame *frame, const QString &message); 83 | virtual bool javaScriptPrompt(QWebFrame *frame, const QString &message, const QString &defaultValue, QString *result); 84 | virtual QString chooseFile(QWebFrame * parentFrame, const QString &suggestedFile); 85 | virtual bool supportsExtension(Extension extension) const; 86 | 87 | private: 88 | QString m_capybaraJavascript; 89 | QString m_userAgent; 90 | bool m_loading; 91 | bool m_failed; 92 | QStringList getAttachedFileNames(); 93 | void loadJavascript(); 94 | void setUserStylesheet(); 95 | bool m_confirmAction; 96 | bool m_promptAction; 97 | QVariantList m_consoleMessages; 98 | QVariantList m_alertMessages; 99 | QVariantList m_confirmMessages; 100 | QString m_prompt_text; 101 | QVariantList m_promptMessages; 102 | QString m_uuid; 103 | WebPageManager *m_manager; 104 | QString m_errorPageMessage; 105 | void setFrameProperties(QWebFrame *, QUrl &, NetworkReplyProxy *); 106 | QPoint m_mousePosition; 107 | QList m_modalResponses; 108 | QStringList m_modalMessages; 109 | void addModalMessage(bool, const QString &, const QRegExp &); 110 | QWebFrame* m_currentFrameParent; 111 | }; 112 | 113 | #endif //_WEBPAGE_H 114 | 115 | -------------------------------------------------------------------------------- /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 | #include "UnknownUrlHandler.h" 11 | 12 | class WebPage; 13 | class NetworkCookieJar; 14 | class NetworkAccessManager; 15 | class BlacklistedRequestHandler; 16 | class CustomHeadersRequestHandler; 17 | 18 | class WebPageManager : public QObject { 19 | Q_OBJECT 20 | 21 | public: 22 | WebPageManager(QObject *parent = 0); 23 | void append(WebPage *value); 24 | QList pages() const; 25 | void setCurrentPage(WebPage *); 26 | WebPage *currentPage() const; 27 | WebPage *createPage(); 28 | void removePage(WebPage *); 29 | void setIgnoreSslErrors(bool); 30 | bool ignoreSslErrors(); 31 | void setTimeout(int); 32 | int getTimeout(); 33 | void reset(); 34 | NetworkCookieJar *cookieJar(); 35 | bool isLoading() const; 36 | QDebug log() const; 37 | void enableLogging(); 38 | void replyFinished(QNetworkReply *reply); 39 | NetworkAccessManager *networkAccessManager(); 40 | void setUrlBlacklist(const QStringList &); 41 | void addHeader(QString, QString); 42 | void setUnknownUrlMode(UnknownUrlHandler::Mode); 43 | void allowUrl(const QString &); 44 | void blockUrl(const QString &); 45 | 46 | public slots: 47 | void emitLoadStarted(); 48 | void setPageStatus(bool); 49 | void requestCreated(QByteArray &url, QNetworkReply *reply); 50 | void handleReplyFinished(); 51 | void replyDestroyed(QObject *); 52 | 53 | signals: 54 | void pageFinished(bool); 55 | void loadStarted(); 56 | 57 | private: 58 | void emitPageFinished(); 59 | static void handleDebugMessage(QtMsgType type, const char *message); 60 | void initOfflineWebApplicationCache(); 61 | 62 | QList m_pages; 63 | QList m_pendingReplies; 64 | WebPage *m_currentPage; 65 | bool m_ignoreSslErrors; 66 | NetworkCookieJar *m_cookieJar; 67 | QSet m_started; 68 | bool m_success; 69 | bool m_loggingEnabled; 70 | bool m_isCacheInitialized; 71 | QFile *m_ignoredOutput; 72 | int m_timeout; 73 | NetworkAccessManager *m_networkAccessManager; 74 | BlacklistedRequestHandler *m_blacklistedRequestHandler; 75 | CustomHeadersRequestHandler *m_customHeadersRequestHandler; 76 | UnknownUrlHandler *m_unknownUrlHandler; 77 | }; 78 | 79 | #endif // _WEBPAGEMANAGER_H 80 | -------------------------------------------------------------------------------- /src/WindowClose.cpp: -------------------------------------------------------------------------------- 1 | #include "WindowClose.h" 2 | #include "WebPage.h" 3 | 4 | WindowClose::WindowClose(WebPageManager *manager, QStringList &arguments, QObject *parent) : WindowCommand(manager, arguments, parent) { 5 | } 6 | 7 | void WindowClose::windowFound(WebPage *page) { 8 | page->remove(); 9 | finish(true); 10 | } 11 | -------------------------------------------------------------------------------- /src/WindowClose.h: -------------------------------------------------------------------------------- 1 | #include "WindowCommand.h" 2 | 3 | class WindowClose : public WindowCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | WindowClose(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | 9 | protected: 10 | virtual void windowFound(WebPage *); 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /src/WindowCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "WindowCommand.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "ErrorMessage.h" 5 | 6 | WindowCommand::WindowCommand(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void WindowCommand::start() { 10 | findWindow(arguments()[0]); 11 | } 12 | 13 | void WindowCommand::findWindow(QString selector) { 14 | foreach(WebPage *page, manager()->pages()) { 15 | if (page->matchesWindowSelector(selector)) { 16 | windowFound(page); 17 | return; 18 | } 19 | } 20 | 21 | windowNotFound(); 22 | } 23 | 24 | void WindowCommand::windowNotFound() { 25 | finish(false, 26 | new ErrorMessage("NoSuchWindowError", "Unable to locate window.")); 27 | } 28 | -------------------------------------------------------------------------------- /src/WindowCommand.h: -------------------------------------------------------------------------------- 1 | #ifndef WINDOW_COMMAND_H 2 | #define WINDOW_COMMAND_H 3 | 4 | #include "SocketCommand.h" 5 | 6 | class WindowCommand : public SocketCommand { 7 | Q_OBJECT 8 | 9 | public: 10 | WindowCommand(WebPageManager *, QStringList &arguments, QObject *parent = 0); 11 | virtual void start(); 12 | 13 | protected: 14 | virtual void windowFound(WebPage *) = 0; 15 | 16 | private: 17 | void findWindow(QString); 18 | void windowNotFound(); 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/WindowFocus.cpp: -------------------------------------------------------------------------------- 1 | #include "WindowFocus.h" 2 | #include "WebPage.h" 3 | 4 | WindowFocus::WindowFocus(WebPageManager *manager, QStringList &arguments, QObject *parent) : WindowCommand(manager, arguments, parent) { 5 | } 6 | 7 | void WindowFocus::windowFound(WebPage *page) { 8 | page->setFocus(); 9 | finish(true); 10 | } 11 | -------------------------------------------------------------------------------- /src/WindowFocus.h: -------------------------------------------------------------------------------- 1 | #include "WindowCommand.h" 2 | 3 | class WindowFocus : public WindowCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | WindowFocus(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | 9 | protected: 10 | virtual void windowFound(WebPage *); 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /src/WindowMaximize.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "WindowMaximize.h" 3 | #include "WebPage.h" 4 | #include "WebPageManager.h" 5 | 6 | WindowMaximize::WindowMaximize(WebPageManager *manager, QStringList &arguments, QObject *parent) : WindowCommand(manager, arguments, parent) { 7 | } 8 | 9 | void WindowMaximize::windowFound(WebPage *page) { 10 | QDesktopWidget *desktop = QApplication::desktop(); 11 | QRect area = desktop->availableGeometry(); 12 | page->resize(area.width(), area.height()); 13 | finish(true); 14 | } 15 | -------------------------------------------------------------------------------- /src/WindowMaximize.h: -------------------------------------------------------------------------------- 1 | #include "WindowCommand.h" 2 | 3 | class WindowMaximize : public WindowCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | WindowMaximize(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | 9 | protected: 10 | virtual void windowFound(WebPage *); 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /src/WindowOpen.cpp: -------------------------------------------------------------------------------- 1 | #include "WindowOpen.h" 2 | #include "SocketCommand.h" 3 | #include "WebPage.h" 4 | #include "WebPageManager.h" 5 | 6 | WindowOpen::WindowOpen(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void WindowOpen::start() { 10 | manager()->createPage(); 11 | finish(true); 12 | } 13 | -------------------------------------------------------------------------------- /src/WindowOpen.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class WindowOpen : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | WindowOpen(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/WindowResize.cpp: -------------------------------------------------------------------------------- 1 | #include "WindowResize.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | 5 | WindowResize::WindowResize(WebPageManager *manager, QStringList &arguments, QObject *parent) : WindowCommand(manager, arguments, parent) { 6 | } 7 | 8 | void WindowResize::windowFound(WebPage *page) { 9 | int width = arguments()[1].toInt(); 10 | int height = arguments()[2].toInt(); 11 | 12 | page->resize(width, height); 13 | 14 | finish(true); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/WindowResize.h: -------------------------------------------------------------------------------- 1 | #include "WindowCommand.h" 2 | 3 | class WindowResize : public WindowCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | WindowResize(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | 9 | protected: 10 | virtual void windowFound(WebPage *); 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /src/WindowSize.cpp: -------------------------------------------------------------------------------- 1 | #include "WindowSize.h" 2 | #include "WebPage.h" 3 | #include "WebPageManager.h" 4 | #include "JsonSerializer.h" 5 | 6 | WindowSize::WindowSize(WebPageManager *manager, QStringList &arguments, QObject *parent) : WindowCommand(manager, arguments, parent) { 7 | } 8 | 9 | void WindowSize::windowFound(WebPage *page) { 10 | QSize size = page->viewportSize(); 11 | QVariantList elements; 12 | elements << size.width(); 13 | elements << size.height(); 14 | JsonSerializer serializer; 15 | QByteArray json = serializer.serialize(elements); 16 | finish(true, json); 17 | } 18 | -------------------------------------------------------------------------------- /src/WindowSize.h: -------------------------------------------------------------------------------- 1 | #include "WindowCommand.h" 2 | 3 | class WindowSize : public WindowCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | WindowSize(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | 9 | protected: 10 | virtual void windowFound(WebPage *); 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/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(EvaluateAsync) 12 | CHECK_COMMAND(Execute) 13 | CHECK_COMMAND(FrameFocus) 14 | CHECK_COMMAND(Header) 15 | CHECK_COMMAND(Render) 16 | CHECK_COMMAND(Body) 17 | CHECK_COMMAND(Status) 18 | CHECK_COMMAND(Headers) 19 | CHECK_COMMAND(SetCookie) 20 | CHECK_COMMAND(ClearCookies) 21 | CHECK_COMMAND(GetCookies) 22 | CHECK_COMMAND(SetProxy) 23 | CHECK_COMMAND(ConsoleMessages) 24 | CHECK_COMMAND(CurrentUrl) 25 | CHECK_COMMAND(WindowResize) 26 | CHECK_COMMAND(IgnoreSslErrors) 27 | CHECK_COMMAND(SetSkipImageLoading) 28 | CHECK_COMMAND(WindowFocus) 29 | CHECK_COMMAND(GetWindowHandles) 30 | CHECK_COMMAND(GetWindowHandle) 31 | CHECK_COMMAND(Authenticate) 32 | CHECK_COMMAND(EnableLogging) 33 | CHECK_COMMAND(SetConfirmAction) 34 | CHECK_COMMAND(SetPromptAction) 35 | CHECK_COMMAND(SetPromptText) 36 | CHECK_COMMAND(ClearPromptText) 37 | CHECK_COMMAND(JavascriptAlertMessages) 38 | CHECK_COMMAND(JavascriptConfirmMessages) 39 | CHECK_COMMAND(JavascriptPromptMessages) 40 | CHECK_COMMAND(GetTimeout) 41 | CHECK_COMMAND(SetTimeout) 42 | CHECK_COMMAND(SetUrlBlacklist) 43 | CHECK_COMMAND(Title) 44 | CHECK_COMMAND(Version) 45 | CHECK_COMMAND(FindCss) 46 | CHECK_COMMAND(WindowClose) 47 | CHECK_COMMAND(WindowOpen) 48 | CHECK_COMMAND(WindowSize) 49 | CHECK_COMMAND(WindowMaximize) 50 | CHECK_COMMAND(GoBack) 51 | CHECK_COMMAND(GoForward) 52 | CHECK_COMMAND(Refresh) 53 | CHECK_COMMAND(AcceptAlert) 54 | CHECK_COMMAND(FindModal) 55 | CHECK_COMMAND(SetUnknownUrlMode) 56 | CHECK_COMMAND(AllowUrl) 57 | CHECK_COMMAND(BlockUrl) 58 | CHECK_COMMAND(FrameTitle) 59 | CHECK_COMMAND(FrameUrl) 60 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Server.h" 2 | #include "IgnoreDebugOutput.h" 3 | #include "StdinNotifier.h" 4 | #include 5 | #include 6 | #include 7 | #ifdef Q_OS_UNIX 8 | #include 9 | #endif 10 | 11 | int main(int argc, char **argv) { 12 | #ifdef Q_OS_UNIX 13 | if (setpgid(0, 0) < 0) { 14 | std::cerr << "Unable to set new process group." << std::endl; 15 | return 1; 16 | } 17 | #endif 18 | 19 | #ifdef Q_OS_MAC 20 | QApplication::setStyle(QStyleFactory::create("Fusion")); 21 | #endif 22 | 23 | QApplication app(argc, argv); 24 | app.setApplicationName("capybara-webkit"); 25 | app.setOrganizationName("thoughtbot, inc"); 26 | app.setOrganizationDomain("thoughtbot.com"); 27 | 28 | StdinNotifier notifier; 29 | QObject::connect(¬ifier, SIGNAL(eof()), &app, SLOT(quit())); 30 | 31 | ignoreDebugOutput(); 32 | Server server(0); 33 | 34 | if (server.start()) { 35 | std::cout << "Capybara-webkit server started, listening on port: " << server.server_port() << std::endl; 36 | return app.exec(); 37 | } else { 38 | std::cerr << "Couldn't start capybara-webkit server" << std::endl; 39 | return 1; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/pointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtbot/capybara-webkit/f429d668568ff7349f5e23a085df7fcf1c431fa7/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 | QMAKE_CXXFLAGS += $$(CXXFLAGS) 5 | QMAKE_CFLAGS += $$(CFLAGS) 6 | QMAKE_LFLAGS += $$(LDFLAGS) 7 | PROJECT_DIR = $$_PRO_FILE_PWD_ 8 | BUILD_DIR = $${PROJECT_DIR}/build 9 | PRECOMPILED_DIR = $${BUILD_DIR} 10 | OBJECTS_DIR = $${BUILD_DIR} 11 | MOC_DIR = $${BUILD_DIR} 12 | HEADERS = \ 13 | FrameUrl.h \ 14 | FrameTitle.h \ 15 | BlockUrl.h \ 16 | AllowUrl.h \ 17 | SetUnknownUrlMode.h \ 18 | FindModal.h \ 19 | AcceptAlert.h \ 20 | GoForward.h \ 21 | GoBack.h \ 22 | Refresh.h \ 23 | WindowMaximize.h \ 24 | WindowSize.h \ 25 | WindowCommand.h \ 26 | WindowOpen.h \ 27 | WindowClose.h \ 28 | Version.h \ 29 | EnableLogging.h \ 30 | Authenticate.h \ 31 | SetConfirmAction.h \ 32 | SetPromptAction.h \ 33 | SetPromptText.h \ 34 | ClearPromptText.h \ 35 | JavascriptAlertMessages.h \ 36 | JavascriptConfirmMessages.h \ 37 | JavascriptPromptMessages.h \ 38 | IgnoreSslErrors.h \ 39 | WindowResize.h \ 40 | CurrentUrl.h \ 41 | ConsoleMessages.h \ 42 | WebPage.h \ 43 | Server.h \ 44 | Connection.h \ 45 | Command.h \ 46 | SocketCommand.h \ 47 | Visit.h \ 48 | Reset.h \ 49 | Node.h \ 50 | JavascriptInvocation.h \ 51 | Evaluate.h \ 52 | EvaluateAsync.h \ 53 | Execute.h \ 54 | FrameFocus.h \ 55 | Response.h \ 56 | NetworkAccessManager.h \ 57 | NetworkCookieJar.h \ 58 | Header.h \ 59 | Render.h \ 60 | Body.h \ 61 | Status.h \ 62 | Headers.h \ 63 | UnsupportedContentHandler.h \ 64 | SetCookie.h \ 65 | ClearCookies.h \ 66 | GetCookies.h \ 67 | CommandParser.h \ 68 | CommandFactory.h \ 69 | SetProxy.h \ 70 | NullCommand.h \ 71 | PageLoadingCommand.h \ 72 | SetSkipImageLoading.h \ 73 | WebPageManager.h \ 74 | WindowFocus.h \ 75 | GetWindowHandles.h \ 76 | GetWindowHandle.h \ 77 | GetTimeout.h \ 78 | SetTimeout.h \ 79 | TimeoutCommand.h \ 80 | SetUrlBlacklist.h \ 81 | NoOpReply.h \ 82 | JsonSerializer.h \ 83 | InvocationResult.h \ 84 | ErrorMessage.h \ 85 | Title.h \ 86 | FindCss.h \ 87 | JavascriptCommand.h \ 88 | FindXpath.h \ 89 | NetworkReplyProxy.h \ 90 | IgnoreDebugOutput.h \ 91 | StdinNotifier.h \ 92 | RequestHandler.h \ 93 | BlacklistedRequestHandler.h \ 94 | MissingContentHeaderRequestHandler.h \ 95 | CustomHeadersRequestHandler.h \ 96 | NetworkRequestFactory.h \ 97 | UnknownUrlHandler.h 98 | 99 | SOURCES = \ 100 | FrameUrl.cpp \ 101 | FrameTitle.cpp \ 102 | BlockUrl.cpp \ 103 | AllowUrl.cpp \ 104 | SetUnknownUrlMode.cpp \ 105 | FindModal.cpp \ 106 | AcceptAlert.cpp \ 107 | GoForward.cpp \ 108 | GoBack.cpp \ 109 | Refresh.cpp \ 110 | WindowMaximize.cpp \ 111 | WindowSize.cpp \ 112 | WindowCommand.cpp \ 113 | WindowOpen.cpp \ 114 | WindowClose.cpp \ 115 | Version.cpp \ 116 | EnableLogging.cpp \ 117 | Authenticate.cpp \ 118 | SetConfirmAction.cpp \ 119 | SetPromptAction.cpp \ 120 | SetPromptText.cpp \ 121 | ClearPromptText.cpp \ 122 | JavascriptAlertMessages.cpp \ 123 | JavascriptConfirmMessages.cpp \ 124 | JavascriptPromptMessages.cpp \ 125 | IgnoreSslErrors.cpp \ 126 | WindowResize.cpp \ 127 | CurrentUrl.cpp \ 128 | ConsoleMessages.cpp \ 129 | main.cpp \ 130 | WebPage.cpp \ 131 | Server.cpp \ 132 | Connection.cpp \ 133 | Command.cpp \ 134 | SocketCommand.cpp \ 135 | Visit.cpp \ 136 | Reset.cpp \ 137 | Node.cpp \ 138 | JavascriptInvocation.cpp \ 139 | Evaluate.cpp \ 140 | EvaluateAsync.cpp \ 141 | Execute.cpp \ 142 | FrameFocus.cpp \ 143 | Response.cpp \ 144 | NetworkAccessManager.cpp \ 145 | NetworkCookieJar.cpp \ 146 | Header.cpp \ 147 | Render.cpp \ 148 | body.cpp \ 149 | Status.cpp \ 150 | Headers.cpp \ 151 | UnsupportedContentHandler.cpp \ 152 | SetCookie.cpp \ 153 | ClearCookies.cpp \ 154 | GetCookies.cpp \ 155 | CommandParser.cpp \ 156 | CommandFactory.cpp \ 157 | SetProxy.cpp \ 158 | NullCommand.cpp \ 159 | PageLoadingCommand.cpp \ 160 | SetTimeout.cpp \ 161 | GetTimeout.cpp \ 162 | SetSkipImageLoading.cpp \ 163 | WebPageManager.cpp \ 164 | WindowFocus.cpp \ 165 | GetWindowHandles.cpp \ 166 | GetWindowHandle.cpp \ 167 | TimeoutCommand.cpp \ 168 | SetUrlBlacklist.cpp \ 169 | NoOpReply.cpp \ 170 | JsonSerializer.cpp \ 171 | InvocationResult.cpp \ 172 | ErrorMessage.cpp \ 173 | Title.cpp \ 174 | FindCss.cpp \ 175 | JavascriptCommand.cpp \ 176 | FindXpath.cpp \ 177 | NetworkReplyProxy.cpp \ 178 | IgnoreDebugOutput.cpp \ 179 | StdinNotifier.cpp \ 180 | RequestHandler.cpp \ 181 | BlacklistedRequestHandler.cpp \ 182 | MissingContentHeaderRequestHandler.cpp \ 183 | CustomHeadersRequestHandler.cpp \ 184 | NetworkRequestFactory.cpp \ 185 | UnknownUrlHandler.cpp 186 | 187 | RESOURCES = webkit_server.qrc 188 | QT += network 189 | greaterThan(QT_MAJOR_VERSION, 4) { 190 | qtHaveModule(webkitwidgets) { 191 | QT += webkitwidgets 192 | } else { 193 | error("No QtWebKit installation found. QtWebKit is no longer included with Qt 5.6, so you may need to install it separately.") 194 | } 195 | } else { 196 | QT += webkit 197 | } 198 | lessThan(QT_MAJOR_VERSION, 5) { 199 | lessThan(QT_MINOR_VERSION, 8) { 200 | error(At least Qt 4.8.0 is required to run capybara-webkit.) 201 | } 202 | } 203 | CONFIG += console precompile_header 204 | CONFIG -= app_bundle 205 | PRECOMPILED_HEADER = stable.h 206 | macx { 207 | QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.9 208 | } 209 | -------------------------------------------------------------------------------- /src/webkit_server.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | capybara.js 4 | pointer.png 5 | 6 | 7 | -------------------------------------------------------------------------------- /templates/Command.cpp: -------------------------------------------------------------------------------- 1 | #include "NAME.h" 2 | #include "SocketCommand.h" 3 | #include "WebPage.h" 4 | #include "WebPageManager.h" 5 | 6 | NAME::NAME(WebPageManager *manager, QStringList &arguments, QObject *parent) : SocketCommand(manager, arguments, parent) { 7 | } 8 | 9 | void NAME::start() { 10 | } 11 | -------------------------------------------------------------------------------- /templates/Command.h: -------------------------------------------------------------------------------- 1 | #include "SocketCommand.h" 2 | 3 | class NAME : public SocketCommand { 4 | Q_OBJECT 5 | 6 | public: 7 | NAME(WebPageManager *, QStringList &arguments, QObject *parent = 0); 8 | virtual void start(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /test/testignoredebugoutput.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "../src/IgnoreDebugOutput.h" 6 | 7 | #define MAX_LEN 40 8 | 9 | class TestIgnoreDebugOutput: public QObject { 10 | Q_OBJECT 11 | 12 | private slots: 13 | void testIgnoreDebugOutput(); 14 | }; 15 | 16 | void TestIgnoreDebugOutput::testIgnoreDebugOutput() { 17 | char buffer[MAX_LEN+1] = {0}; 18 | int out_pipe[2]; 19 | int saved_stdout; 20 | 21 | saved_stdout = dup(STDOUT_FILENO); 22 | 23 | QVERIFY(pipe(out_pipe) == 0); 24 | 25 | dup2(out_pipe[1], STDOUT_FILENO); 26 | close(out_pipe[1]); 27 | 28 | long flags = fcntl(out_pipe[0], F_GETFL); 29 | flags |= O_NONBLOCK; 30 | fcntl(out_pipe[0], F_SETFL, flags); 31 | 32 | ignoreDebugOutput(); 33 | 34 | qDebug() << "Message"; 35 | fflush(stdout); 36 | 37 | read(out_pipe[0], buffer, MAX_LEN); 38 | 39 | dup2(saved_stdout, STDOUT_FILENO); 40 | 41 | QCOMPARE(QString(buffer), QString("")); 42 | } 43 | 44 | QTEST_MAIN(TestIgnoreDebugOutput) 45 | #include "testignoredebugoutput.moc" 46 | -------------------------------------------------------------------------------- /test/testwebkitserver.pro: -------------------------------------------------------------------------------- 1 | SOURCES = testignoredebugoutput.cpp 2 | OBJECTS += ../src/build/IgnoreDebugOutput.o 3 | QT += testlib 4 | CONFIG += testcase console 5 | CONFIG -= app_bundle 6 | macx { 7 | QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.9 8 | } 9 | -------------------------------------------------------------------------------- /vagrant_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export DISPLAY=:99 4 | 5 | apt-get -y update 6 | apt-get -y install ruby1.9.1-dev 7 | 8 | if [ -z `which make` ]; then apt-get -y install build-essential; fi 9 | if [ -z `which qmake` ]; then apt-get -y install libqt4-dev libicu48; fi 10 | if [ -z `which git` ]; then apt-get -y install git-core; fi 11 | if [ -z `which xml2-config` ]; then apt-get -y install libxml2-dev; fi 12 | if [ -z `which xslt-config` ]; then apt-get -y install libxslt-dev; fi 13 | if [ -z `which convert` ]; then apt-get -y install imagemagick; fi 14 | if [ -z `which firefox` ]; then apt-get -y install firefox; fi 15 | 16 | if [ -z `which bundle` ]; 17 | then 18 | gem install bundler 19 | cd /vagrant 20 | bundle 21 | fi 22 | 23 | if [ ! -f /etc/init.d/xvfb ]; 24 | then 25 | apt-get -y install xvfb 26 | echo "export DISPLAY=${DISPLAY}" >> /home/vagrant/.bashrc 27 | tee /etc/init.d/xvfb <<-EOF 28 | #!/bin/bash 29 | 30 | XVFB=/usr/bin/Xvfb 31 | XVFBARGS="\$DISPLAY -ac -screen 0 1024x768x16" 32 | PIDFILE=\${HOME}/xvfb_\${DISPLAY:1}.pid 33 | case "\$1" in 34 | start) 35 | echo -n "Starting virtual X frame buffer: Xvfb" 36 | /sbin/start-stop-daemon --start --quiet --pidfile \$PIDFILE --make-pidfile --background --exec \$XVFB -- \$XVFBARGS 37 | echo "." 38 | ;; 39 | stop) 40 | echo -n "Stopping virtual X frame buffer: Xvfb" 41 | /sbin/start-stop-daemon --stop --quiet --pidfile \$PIDFILE 42 | echo "." 43 | ;; 44 | restart) 45 | \$0 stop 46 | \$0 start 47 | ;; 48 | *) 49 | echo "Usage: /etc/init.d/xvfb {start|stop|restart}" 50 | exit 1 51 | esac 52 | exit 0 53 | EOF 54 | 55 | chmod +x /etc/init.d/xvfb 56 | fi 57 | 58 | /etc/init.d/xvfb start 59 | -------------------------------------------------------------------------------- /webkit_server.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | CONFIG += ordered 3 | SUBDIRS += src/webkit_server.pro 4 | test { 5 | SUBDIRS += test/testwebkitserver.pro 6 | } 7 | --------------------------------------------------------------------------------