2
require "capybara/poltergeist/utility"
5
module Capybara::Poltergeist
7
PHANTOMJS_SCRIPT = File.expand_path('../client/compiled/main.js', __FILE__)
8
PHANTOMJS_VERSION = ['>= 1.8.1', '< 3.0']
9
PHANTOMJS_NAME = 'phantomjs'
11
KILL_TIMEOUT = 2 # seconds
19
# Returns a proc, that when called will attempt to kill the given process.
20
# This is because implementing ObjectSpace.define_finalizer is tricky.
21
# Hat-Tip to @mperham for describing in detail:
22
# http://www.mikeperham.com/2010/02/24/the-trouble-with-ruby-finalizers/
23
def self.process_killer(pid)
26
Process.kill('KILL', pid)
27
rescue Errno::ESRCH, Errno::ECHILD
32
attr_reader :pid, :server, :path, :window_size, :phantomjs_options
34
def initialize(server, options = {})
36
@path = Cliver::detect((options[:path] || PHANTOMJS_NAME), *['>=2.1.0', '< 3.0'])
37
@path ||= Cliver::detect!((options[:path] || PHANTOMJS_NAME), *PHANTOMJS_VERSION).tap do
38
warn "You're running an old version of PhantomJS, update to >= 2.1.1 for a better experience."
41
@window_size = options[:window_size] || [1024, 768]
42
@phantomjs_options = options[:phantomjs_options] || []
43
@phantomjs_logger = options[:phantomjs_logger] || $stdout
47
# do the work in a separate thread, to avoid stomping on $!,
48
# since other libraries depend on it directly.
50
stop if Process.pid == pid
56
@read_io, @write_io = IO.pipe
57
@out_thread = Thread.new {
58
while !@read_io.eof? && data = @read_io.readpartial(1024)
59
@phantomjs_logger.write(data)
64
process_options[:pgroup] = true unless Capybara::Poltergeist.windows?
67
@pid = Process.spawn(*command.map(&:to_s), process_options)
68
ObjectSpace.define_finalizer(self, self.class.process_killer(@pid))
77
ObjectSpace.undefine_finalizer(self)
88
parts.concat phantomjs_options
89
parts << PHANTOMJS_SCRIPT
91
parts.concat window_size
97
# This abomination is because JRuby doesn't support the :out option of
98
# Process.spawn. To be honest it works pretty bad with pipes too, because
99
# we ought close writing end in parent process immediately but JRuby will
100
# lose all the output from child. Process.popen can be used here and seems
101
# it works with JRuby but I've experienced strange mistakes on Rubinius.
105
STDOUT.reopen(@write_io)
115
if Capybara::Poltergeist.windows?
116
Process.kill('KILL', pid)
118
Process.kill('TERM', pid)
120
Timeout.timeout(KILL_TIMEOUT) { Process.wait(pid) }
121
rescue Timeout::Error
122
Process.kill('KILL', pid)
126
rescue Errno::ESRCH, Errno::ECHILD
132
# We grab all the output from PhantomJS like console.log in another thread
133
# and when PhantomJS crashes we try to restart it. In order to do it we stop
134
# server and client and on JRuby see this error `IOError: Stream closed`.
135
# It happens because JRuby tries to close pipe and it is blocked on `eof?`
136
# or `readpartial` call. The error is raised in the related thread and it's
137
# not actually main thread but the thread that listens to the output. That's
138
# why if you put some debug code after `rescue IOError` it won't be shown.
139
# In fact the main thread will continue working after the error even if we
140
# don't use `rescue`. The first attempt to fix it was a try not to block on
141
# IO, but looks like similar issue appers after JRuby upgrade. Perhaps the
142
# only way to fix it is catching the exception what this method overall does.
144
[@write_io, @read_io].each do |io|
146
io.close unless io.closed?
148
raise unless RUBY_ENGINE == 'jruby'