ferrum 0.10.1 → 0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +261 -28
- data/lib/ferrum/browser/binary.rb +46 -0
- data/lib/ferrum/browser/client.rb +15 -12
- data/lib/ferrum/browser/command.rb +7 -8
- data/lib/ferrum/browser/options/base.rb +1 -7
- data/lib/ferrum/browser/options/chrome.rb +17 -10
- data/lib/ferrum/browser/options/firefox.rb +11 -4
- data/lib/ferrum/browser/process.rb +41 -35
- data/lib/ferrum/browser/subscriber.rb +1 -3
- data/lib/ferrum/browser/web_socket.rb +9 -12
- data/lib/ferrum/browser/xvfb.rb +4 -8
- data/lib/ferrum/browser.rb +49 -12
- data/lib/ferrum/context.rb +12 -4
- data/lib/ferrum/contexts.rb +13 -9
- data/lib/ferrum/cookies.rb +10 -9
- data/lib/ferrum/errors.rb +115 -0
- data/lib/ferrum/frame/runtime.rb +20 -17
- data/lib/ferrum/frame.rb +32 -24
- data/lib/ferrum/headers.rb +2 -2
- data/lib/ferrum/keyboard.rb +11 -11
- data/lib/ferrum/mouse.rb +8 -7
- data/lib/ferrum/network/auth_request.rb +7 -2
- data/lib/ferrum/network/exchange.rb +14 -10
- data/lib/ferrum/network/intercepted_request.rb +10 -8
- data/lib/ferrum/network/request.rb +5 -0
- data/lib/ferrum/network/response.rb +4 -4
- data/lib/ferrum/network.rb +124 -35
- data/lib/ferrum/node.rb +98 -40
- data/lib/ferrum/page/animation.rb +15 -0
- data/lib/ferrum/page/frames.rb +46 -15
- data/lib/ferrum/page/screenshot.rb +53 -67
- data/lib/ferrum/page/stream.rb +38 -0
- data/lib/ferrum/page/tracing.rb +71 -0
- data/lib/ferrum/page.rb +88 -34
- data/lib/ferrum/proxy.rb +58 -0
- data/lib/ferrum/{rbga.rb → rgba.rb} +4 -2
- data/lib/ferrum/target.rb +1 -0
- data/lib/ferrum/utils/attempt.rb +20 -0
- data/lib/ferrum/utils/elapsed_time.rb +27 -0
- data/lib/ferrum/utils/platform.rb +28 -0
- data/lib/ferrum/version.rb +1 -1
- data/lib/ferrum.rb +4 -140
- metadata +65 -50
@@ -36,18 +36,27 @@ module Ferrum
|
|
36
36
|
"metrics-recording-only" => nil,
|
37
37
|
"safebrowsing-disable-auto-update" => nil,
|
38
38
|
"password-store" => "basic",
|
39
|
-
|
39
|
+
"no-startup-window" => nil
|
40
|
+
# NOTE: --no-sandbox is not needed if you properly setup a user in the container.
|
40
41
|
# https://github.com/ebidel/lighthouse-ci/blob/master/builder/Dockerfile#L35-L40
|
41
42
|
# "no-sandbox" => nil,
|
42
43
|
}.freeze
|
43
44
|
|
44
45
|
MAC_BIN_PATH = [
|
45
|
-
"/Applications/
|
46
|
-
"/Applications/
|
46
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
47
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium"
|
47
48
|
].freeze
|
48
|
-
LINUX_BIN_PATH = %w[
|
49
|
-
google-chrome
|
50
|
-
|
49
|
+
LINUX_BIN_PATH = %w[chrome google-chrome google-chrome-stable google-chrome-beta
|
50
|
+
chromium chromium-browser google-chrome-unstable].freeze
|
51
|
+
WINDOWS_BIN_PATH = [
|
52
|
+
"C:/Program Files/Google/Chrome/Application/chrome.exe",
|
53
|
+
"C:/Program Files/Google/Chrome Dev/Application/chrome.exe"
|
54
|
+
].freeze
|
55
|
+
PLATFORM_PATH = {
|
56
|
+
mac: MAC_BIN_PATH,
|
57
|
+
windows: WINDOWS_BIN_PATH,
|
58
|
+
linux: LINUX_BIN_PATH
|
59
|
+
}.freeze
|
51
60
|
|
52
61
|
def merge_required(flags, options, user_data_dir)
|
53
62
|
port = options.fetch(:port, BROWSER_PORT)
|
@@ -55,14 +64,12 @@ module Ferrum
|
|
55
64
|
flags.merge("remote-debugging-port" => port,
|
56
65
|
"remote-debugging-address" => host,
|
57
66
|
# Doesn't work on MacOS, so we need to set it by CDP
|
58
|
-
"window-size" => options[:window_size]
|
67
|
+
"window-size" => options[:window_size]&.join(","),
|
59
68
|
"user-data-dir" => user_data_dir)
|
60
69
|
end
|
61
70
|
|
62
71
|
def merge_default(flags, options)
|
63
|
-
unless options.fetch(:headless, true)
|
64
|
-
defaults = except("headless", "disable-gpu")
|
65
|
-
end
|
72
|
+
defaults = except("headless", "disable-gpu") unless options.fetch(:headless, true)
|
66
73
|
|
67
74
|
defaults ||= DEFAULT_OPTIONS
|
68
75
|
defaults.merge(flags)
|
@@ -5,13 +5,22 @@ module Ferrum
|
|
5
5
|
module Options
|
6
6
|
class Firefox < Base
|
7
7
|
DEFAULT_OPTIONS = {
|
8
|
-
"headless" => nil
|
8
|
+
"headless" => nil
|
9
9
|
}.freeze
|
10
10
|
|
11
11
|
MAC_BIN_PATH = [
|
12
12
|
"/Applications/Firefox.app/Contents/MacOS/firefox-bin"
|
13
13
|
].freeze
|
14
14
|
LINUX_BIN_PATH = %w[firefox].freeze
|
15
|
+
WINDOWS_BIN_PATH = [
|
16
|
+
"C:/Program Files/Firefox Developer Edition/firefox.exe",
|
17
|
+
"C:/Program Files/Mozilla Firefox/firefox.exe"
|
18
|
+
].freeze
|
19
|
+
PLATFORM_PATH = {
|
20
|
+
mac: MAC_BIN_PATH,
|
21
|
+
windows: WINDOWS_BIN_PATH,
|
22
|
+
linux: LINUX_BIN_PATH
|
23
|
+
}.freeze
|
15
24
|
|
16
25
|
def merge_required(flags, options, user_data_dir)
|
17
26
|
port = options.fetch(:port, BROWSER_PORT)
|
@@ -21,9 +30,7 @@ module Ferrum
|
|
21
30
|
end
|
22
31
|
|
23
32
|
def merge_default(flags, options)
|
24
|
-
unless options.fetch(:headless, true)
|
25
|
-
defaults = except("headless")
|
26
|
-
end
|
33
|
+
defaults = except("headless") unless options.fetch(:headless, true)
|
27
34
|
|
28
35
|
defaults ||= DEFAULT_OPTIONS
|
29
36
|
defaults.merge(flags)
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "cliver"
|
4
3
|
require "net/http"
|
5
4
|
require "json"
|
6
5
|
require "addressable"
|
@@ -22,7 +21,6 @@ module Ferrum
|
|
22
21
|
:default_user_agent, :browser_version, :protocol_version,
|
23
22
|
:v8_version, :webkit_version, :xvfb
|
24
23
|
|
25
|
-
|
26
24
|
extend Forwardable
|
27
25
|
delegate path: :command
|
28
26
|
|
@@ -32,41 +30,50 @@ module Ferrum
|
|
32
30
|
|
33
31
|
def self.process_killer(pid)
|
34
32
|
proc do
|
35
|
-
|
36
|
-
|
33
|
+
if Utils::Platform.windows?
|
34
|
+
# Process.kill is unreliable on Windows
|
35
|
+
::Process.kill("KILL", pid) unless system("taskkill /f /t /pid #{pid} >NUL 2>NUL")
|
36
|
+
else
|
37
|
+
::Process.kill("USR1", pid)
|
38
|
+
start = Utils::ElapsedTime.monotonic_time
|
39
|
+
while ::Process.wait(pid, ::Process::WNOHANG).nil?
|
40
|
+
sleep(WAIT_KILLED)
|
41
|
+
next unless Utils::ElapsedTime.timeout?(start, KILL_TIMEOUT)
|
42
|
+
|
37
43
|
::Process.kill("KILL", pid)
|
38
|
-
|
39
|
-
|
40
|
-
start = Ferrum.monotonic_time
|
41
|
-
while ::Process.wait(pid, ::Process::WNOHANG).nil?
|
42
|
-
sleep(WAIT_KILLED)
|
43
|
-
next unless Ferrum.timeout?(start, KILL_TIMEOUT)
|
44
|
-
::Process.kill("KILL", pid)
|
45
|
-
::Process.wait(pid)
|
46
|
-
break
|
47
|
-
end
|
44
|
+
::Process.wait(pid)
|
45
|
+
break
|
48
46
|
end
|
49
|
-
rescue Errno::ESRCH, Errno::ECHILD
|
50
47
|
end
|
48
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
49
|
+
# nop
|
51
50
|
end
|
52
51
|
end
|
53
52
|
|
54
53
|
def self.directory_remover(path)
|
55
|
-
proc {
|
54
|
+
proc {
|
55
|
+
begin
|
56
|
+
FileUtils.remove_entry(path)
|
57
|
+
rescue StandardError
|
58
|
+
Errno::ENOENT
|
59
|
+
end
|
60
|
+
}
|
56
61
|
end
|
57
62
|
|
58
63
|
def initialize(options)
|
64
|
+
@pid = @xvfb = @user_data_dir = nil
|
65
|
+
|
59
66
|
if options[:url]
|
60
67
|
url = URI.join(options[:url].to_s, "/json/version")
|
61
68
|
response = JSON.parse(::Net::HTTP.get(url))
|
62
|
-
|
69
|
+
self.ws_url = response["webSocketDebuggerUrl"]
|
63
70
|
parse_browser_versions
|
64
71
|
return
|
65
72
|
end
|
66
73
|
|
67
|
-
@pid = @xvfb = @user_data_dir = nil
|
68
74
|
@logger = options[:logger]
|
69
75
|
@process_timeout = options.fetch(:process_timeout, PROCESS_TIMEOUT)
|
76
|
+
@env = Hash(options[:env])
|
70
77
|
|
71
78
|
tmpdir = Dir.mktmpdir("ferrum_user_data_dir_")
|
72
79
|
ObjectSpace.define_finalizer(self, self.class.directory_remover(tmpdir))
|
@@ -81,7 +88,7 @@ module Ferrum
|
|
81
88
|
begin
|
82
89
|
read_io, write_io = IO.pipe
|
83
90
|
process_options = { in: File::NULL }
|
84
|
-
process_options[:pgroup] = true unless
|
91
|
+
process_options[:pgroup] = true unless Utils::Platform.windows?
|
85
92
|
process_options[:out] = process_options[:err] = write_io
|
86
93
|
|
87
94
|
if @command.xvfb?
|
@@ -89,7 +96,8 @@ module Ferrum
|
|
89
96
|
ObjectSpace.define_finalizer(self, self.class.process_killer(@xvfb.pid))
|
90
97
|
end
|
91
98
|
|
92
|
-
|
99
|
+
env = Hash(@xvfb&.to_env).merge(@env)
|
100
|
+
@pid = ::Process.spawn(env, *@command.to_a, process_options)
|
93
101
|
ObjectSpace.define_finalizer(self, self.class.process_killer(@pid))
|
94
102
|
|
95
103
|
parse_ws_url(read_io, @process_timeout)
|
@@ -128,29 +136,29 @@ module Ferrum
|
|
128
136
|
|
129
137
|
def parse_ws_url(read_io, timeout)
|
130
138
|
output = ""
|
131
|
-
start =
|
139
|
+
start = Utils::ElapsedTime.monotonic_time
|
132
140
|
max_time = start + timeout
|
133
|
-
regexp =
|
134
|
-
while (now =
|
141
|
+
regexp = %r{DevTools listening on (ws://.*)}
|
142
|
+
while (now = Utils::ElapsedTime.monotonic_time) < max_time
|
135
143
|
begin
|
136
144
|
output += read_io.read_nonblock(512)
|
137
145
|
rescue IO::WaitReadable
|
138
|
-
|
146
|
+
read_io.wait_readable(max_time - now)
|
139
147
|
else
|
140
148
|
if output.match(regexp)
|
141
|
-
|
149
|
+
self.ws_url = output.match(regexp)[1].strip
|
142
150
|
break
|
143
151
|
end
|
144
152
|
end
|
145
153
|
end
|
146
154
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
155
|
+
return if ws_url
|
156
|
+
|
157
|
+
@logger&.puts(output)
|
158
|
+
raise ProcessTimeoutError.new(timeout, output)
|
151
159
|
end
|
152
160
|
|
153
|
-
def
|
161
|
+
def ws_url=(url)
|
154
162
|
@ws_url = Addressable::URI.parse(url)
|
155
163
|
@host = @ws_url.host
|
156
164
|
@port = @ws_url.port
|
@@ -171,11 +179,9 @@ module Ferrum
|
|
171
179
|
|
172
180
|
def close_io(*ios)
|
173
181
|
ios.each do |io|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
raise unless RUBY_ENGINE == "jruby"
|
178
|
-
end
|
182
|
+
io.close unless io.closed?
|
183
|
+
rescue IOError
|
184
|
+
raise unless RUBY_ENGINE == "jruby"
|
179
185
|
end
|
180
186
|
end
|
181
187
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "concurrent-ruby"
|
4
|
-
|
5
3
|
module Ferrum
|
6
4
|
class Browser
|
7
5
|
class Subscriber
|
@@ -29,7 +27,7 @@ module Ferrum
|
|
29
27
|
method, params = message.values_at("method", "params")
|
30
28
|
total = @on[method].size
|
31
29
|
@on[method].each_with_index do |block, index|
|
32
|
-
#
|
30
|
+
# In case of multiple callbacks we provide current index and total
|
33
31
|
block.call(params, index, total)
|
34
32
|
end
|
35
33
|
end
|
@@ -21,9 +21,7 @@ module Ferrum
|
|
21
21
|
@driver = ::WebSocket::Driver.client(self, max_length: max_receive_size)
|
22
22
|
@messages = Queue.new
|
23
23
|
|
24
|
-
if SKIP_LOGGING_SCREENSHOTS
|
25
|
-
@screenshot_commands = Concurrent::Hash.new
|
26
|
-
end
|
24
|
+
@screenshot_commands = Concurrent::Hash.new if SKIP_LOGGING_SCREENSHOTS
|
27
25
|
|
28
26
|
@driver.on(:open, &method(:on_open))
|
29
27
|
@driver.on(:message, &method(:on_message))
|
@@ -31,12 +29,13 @@ module Ferrum
|
|
31
29
|
|
32
30
|
@thread = Thread.new do
|
33
31
|
Thread.current.abort_on_exception = true
|
34
|
-
if Thread.current.respond_to?(:report_on_exception=)
|
35
|
-
Thread.current.report_on_exception = true
|
36
|
-
end
|
32
|
+
Thread.current.report_on_exception = true if Thread.current.respond_to?(:report_on_exception=)
|
37
33
|
|
38
34
|
begin
|
39
|
-
|
35
|
+
loop do
|
36
|
+
data = @sock.readpartial(512)
|
37
|
+
break unless data
|
38
|
+
|
40
39
|
@driver.parse(data)
|
41
40
|
end
|
42
41
|
rescue EOFError, Errno::ECONNRESET, Errno::EPIPE
|
@@ -62,7 +61,7 @@ module Ferrum
|
|
62
61
|
output.sub!(/{"data":"(.*)"}/, %("Set FERRUM_LOGGING_SCREENSHOTS=true to see screenshots in Base64"))
|
63
62
|
end
|
64
63
|
|
65
|
-
@logger&.puts(" ◀ #{
|
64
|
+
@logger&.puts(" ◀ #{Utils::ElapsedTime.elapsed_time} #{output}\n")
|
66
65
|
end
|
67
66
|
|
68
67
|
def on_close(_event)
|
@@ -71,13 +70,11 @@ module Ferrum
|
|
71
70
|
end
|
72
71
|
|
73
72
|
def send_message(data)
|
74
|
-
if SKIP_LOGGING_SCREENSHOTS
|
75
|
-
@screenshot_commands[data[:id]] = true
|
76
|
-
end
|
73
|
+
@screenshot_commands[data[:id]] = true if SKIP_LOGGING_SCREENSHOTS
|
77
74
|
|
78
75
|
json = data.to_json
|
79
76
|
@driver.text(json)
|
80
|
-
@logger&.puts("\n\n▶ #{
|
77
|
+
@logger&.puts("\n\n▶ #{Utils::ElapsedTime.elapsed_time} #{json}")
|
81
78
|
end
|
82
79
|
|
83
80
|
def write(data)
|
data/lib/ferrum/browser/xvfb.rb
CHANGED
@@ -4,23 +4,19 @@ module Ferrum
|
|
4
4
|
class Browser
|
5
5
|
class Xvfb
|
6
6
|
NOT_FOUND = "Could not find an executable for the Xvfb. Try to install " \
|
7
|
-
"it with your package manager"
|
7
|
+
"it with your package manager"
|
8
8
|
|
9
9
|
def self.start(*args)
|
10
10
|
new(*args).tap(&:start)
|
11
11
|
end
|
12
12
|
|
13
|
-
def self.xvfb_path
|
14
|
-
Cliver.detect("Xvfb")
|
15
|
-
end
|
16
|
-
|
17
13
|
attr_reader :screen_size, :display_id, :pid
|
18
14
|
|
19
15
|
def initialize(options)
|
20
|
-
@path =
|
21
|
-
raise
|
16
|
+
@path = Binary.find("Xvfb")
|
17
|
+
raise BinaryNotFoundError, NOT_FOUND unless @path
|
22
18
|
|
23
|
-
@screen_size = options.fetch(:window_size, [1024, 768]).join(
|
19
|
+
@screen_size = "#{options.fetch(:window_size, [1024, 768]).join('x')}x24"
|
24
20
|
@display_id = (Time.now.to_f * 1000).to_i % 100_000_000
|
25
21
|
end
|
26
22
|
|
data/lib/ferrum/browser.rb
CHANGED
@@ -3,10 +3,12 @@
|
|
3
3
|
require "base64"
|
4
4
|
require "forwardable"
|
5
5
|
require "ferrum/page"
|
6
|
+
require "ferrum/proxy"
|
6
7
|
require "ferrum/contexts"
|
7
8
|
require "ferrum/browser/xvfb"
|
8
9
|
require "ferrum/browser/process"
|
9
10
|
require "ferrum/browser/client"
|
11
|
+
require "ferrum/browser/binary"
|
10
12
|
|
11
13
|
module Ferrum
|
12
14
|
class Browser
|
@@ -16,21 +18,23 @@ module Ferrum
|
|
16
18
|
|
17
19
|
extend Forwardable
|
18
20
|
delegate %i[default_context] => :contexts
|
19
|
-
delegate %i[targets create_target
|
20
|
-
delegate %i[go_to back forward refresh reload stop wait_for_reload
|
21
|
+
delegate %i[targets create_target page pages windows] => :default_context
|
22
|
+
delegate %i[go_to goto go back forward refresh reload stop wait_for_reload
|
21
23
|
at_css at_xpath css xpath current_url current_title url title
|
22
|
-
body doctype
|
24
|
+
body doctype content=
|
23
25
|
headers cookies network
|
24
26
|
mouse keyboard
|
25
27
|
screenshot pdf mhtml viewport_size
|
26
28
|
frames frame_by main_frame
|
27
29
|
evaluate evaluate_on evaluate_async execute evaluate_func
|
28
30
|
add_script_tag add_style_tag bypass_csp
|
29
|
-
on
|
31
|
+
on position position=
|
32
|
+
playback_rate playback_rate=] => :page
|
30
33
|
delegate %i[default_user_agent] => :process
|
31
34
|
|
32
35
|
attr_reader :client, :process, :contexts, :logger, :js_errors, :pending_connection_errors,
|
33
|
-
:slowmo, :base_url, :options, :window_size, :ws_max_receive_size
|
36
|
+
:slowmo, :base_url, :options, :window_size, :ws_max_receive_size, :proxy_options,
|
37
|
+
:proxy_server
|
34
38
|
attr_writer :timeout
|
35
39
|
|
36
40
|
def initialize(options = nil)
|
@@ -44,16 +48,29 @@ module Ferrum
|
|
44
48
|
@logger, @timeout, @ws_max_receive_size =
|
45
49
|
@options.values_at(:logger, :timeout, :ws_max_receive_size)
|
46
50
|
@js_errors = @options.fetch(:js_errors, false)
|
51
|
+
|
52
|
+
if @options[:proxy]
|
53
|
+
@proxy_options = @options[:proxy]
|
54
|
+
|
55
|
+
if @proxy_options[:server]
|
56
|
+
@proxy_server = Proxy.start(**@proxy_options.slice(:host, :port, :user, :password))
|
57
|
+
@proxy_options.merge!(host: @proxy_server.host, port: @proxy_server.port)
|
58
|
+
end
|
59
|
+
|
60
|
+
@options[:browser_options] ||= {}
|
61
|
+
address = "#{@proxy_options[:host]}:#{@proxy_options[:port]}"
|
62
|
+
@options[:browser_options].merge!("proxy-server" => address)
|
63
|
+
@options[:browser_options].merge!("proxy-bypass-list" => @proxy_options[:bypass]) if @proxy_options[:bypass]
|
64
|
+
end
|
65
|
+
|
47
66
|
@pending_connection_errors = @options.fetch(:pending_connection_errors, true)
|
48
67
|
@slowmo = @options[:slowmo].to_f
|
49
68
|
|
50
|
-
if @options.key?(:base_url)
|
51
|
-
self.base_url = @options[:base_url]
|
52
|
-
end
|
69
|
+
self.base_url = @options[:base_url] if @options.key?(:base_url)
|
53
70
|
|
54
|
-
if ENV
|
55
|
-
|
56
|
-
@logger =
|
71
|
+
if ENV.fetch("FERRUM_DEBUG", nil) && !@logger
|
72
|
+
$stdout.sync = true
|
73
|
+
@logger = $stdout
|
57
74
|
@options[:logger] = @logger
|
58
75
|
end
|
59
76
|
|
@@ -71,12 +88,32 @@ module Ferrum
|
|
71
88
|
@base_url = parsed
|
72
89
|
end
|
73
90
|
|
91
|
+
def create_page(new_context: false)
|
92
|
+
page = if new_context
|
93
|
+
context = contexts.create
|
94
|
+
context.create_page
|
95
|
+
else
|
96
|
+
default_context.create_page
|
97
|
+
end
|
98
|
+
|
99
|
+
block_given? ? yield(page) : page
|
100
|
+
ensure
|
101
|
+
if block_given?
|
102
|
+
page.close
|
103
|
+
context.dispose if new_context
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
74
107
|
def extensions
|
75
108
|
@extensions ||= Array(@options[:extensions]).map do |ext|
|
76
109
|
(ext.is_a?(Hash) && ext[:source]) || File.read(ext)
|
77
110
|
end
|
78
111
|
end
|
79
112
|
|
113
|
+
def evaluate_on_new_document(expression)
|
114
|
+
extensions << expression
|
115
|
+
end
|
116
|
+
|
80
117
|
def timeout
|
81
118
|
@timeout || DEFAULT_TIMEOUT
|
82
119
|
end
|
@@ -116,7 +153,7 @@ module Ferrum
|
|
116
153
|
private
|
117
154
|
|
118
155
|
def start
|
119
|
-
|
156
|
+
Utils::ElapsedTime.start
|
120
157
|
@process = Process.start(@options)
|
121
158
|
@client = Client.new(self, @process.ws_url)
|
122
159
|
@contexts = Contexts.new(self)
|
data/lib/ferrum/context.rb
CHANGED
@@ -9,8 +9,10 @@ module Ferrum
|
|
9
9
|
attr_reader :id, :targets
|
10
10
|
|
11
11
|
def initialize(browser, contexts, id)
|
12
|
-
@
|
13
|
-
@
|
12
|
+
@id = id
|
13
|
+
@browser = browser
|
14
|
+
@contexts = contexts
|
15
|
+
@targets = Concurrent::Map.new
|
14
16
|
@pendings = Concurrent::MVar.new
|
15
17
|
end
|
16
18
|
|
@@ -32,6 +34,7 @@ module Ferrum
|
|
32
34
|
# usually is the last one.
|
33
35
|
def windows(pos = nil, size = 1)
|
34
36
|
raise ArgumentError if pos && !POSITION.include?(pos)
|
37
|
+
|
35
38
|
windows = @targets.values.select(&:window?)
|
36
39
|
windows = windows.send(pos, size) if pos
|
37
40
|
windows.map(&:page)
|
@@ -47,14 +50,15 @@ module Ferrum
|
|
47
50
|
url: "about:blank")
|
48
51
|
target = @pendings.take(@browser.timeout)
|
49
52
|
raise NoSuchTargetError unless target.is_a?(Target)
|
50
|
-
|
53
|
+
|
54
|
+
@targets.put_if_absent(target.id, target)
|
51
55
|
target
|
52
56
|
end
|
53
57
|
|
54
58
|
def add_target(params)
|
55
59
|
target = Target.new(@browser, params)
|
56
60
|
if target.window?
|
57
|
-
@targets
|
61
|
+
@targets.put_if_absent(target.id, target)
|
58
62
|
else
|
59
63
|
@pendings.put(target, @browser.timeout)
|
60
64
|
end
|
@@ -72,6 +76,10 @@ module Ferrum
|
|
72
76
|
@contexts.dispose(@id)
|
73
77
|
end
|
74
78
|
|
79
|
+
def target?(target_id)
|
80
|
+
!!@targets[target_id]
|
81
|
+
end
|
82
|
+
|
75
83
|
def inspect
|
76
84
|
%(#<#{self.class} @id=#{@id.inspect} @targets=#{@targets.inspect} @default_target=#{@default_target.inspect}>)
|
77
85
|
end
|
data/lib/ferrum/contexts.rb
CHANGED
@@ -7,7 +7,7 @@ module Ferrum
|
|
7
7
|
attr_reader :contexts
|
8
8
|
|
9
9
|
def initialize(browser)
|
10
|
-
@contexts = Concurrent::
|
10
|
+
@contexts = Concurrent::Map.new
|
11
11
|
@browser = browser
|
12
12
|
subscribe
|
13
13
|
discover
|
@@ -18,7 +18,9 @@ module Ferrum
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def find_by(target_id:)
|
21
|
-
|
21
|
+
context = nil
|
22
|
+
@contexts.each_value { |c| context = c if c.target?(target_id) }
|
23
|
+
context
|
22
24
|
end
|
23
25
|
|
24
26
|
def create
|
@@ -39,7 +41,11 @@ module Ferrum
|
|
39
41
|
|
40
42
|
def reset
|
41
43
|
@default_context = nil
|
42
|
-
@contexts.
|
44
|
+
@contexts.each_key { |id| dispose(id) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def size
|
48
|
+
@contexts.size
|
43
49
|
end
|
44
50
|
|
45
51
|
private
|
@@ -62,15 +68,13 @@ module Ferrum
|
|
62
68
|
end
|
63
69
|
|
64
70
|
@browser.client.on("Target.targetDestroyed") do |params|
|
65
|
-
|
66
|
-
|
67
|
-
end
|
71
|
+
context = find_by(target_id: params["targetId"])
|
72
|
+
context&.delete_target(params["targetId"])
|
68
73
|
end
|
69
74
|
|
70
75
|
@browser.client.on("Target.targetCrashed") do |params|
|
71
|
-
|
72
|
-
|
73
|
-
end
|
76
|
+
context = find_by(target_id: params["targetId"])
|
77
|
+
context&.delete_target(params["targetId"])
|
74
78
|
end
|
75
79
|
end
|
76
80
|
|
data/lib/ferrum/cookies.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
module Ferrum
|
4
4
|
class Cookies
|
5
5
|
class Cookie
|
6
|
+
attr_reader :attributes
|
7
|
+
|
6
8
|
def initialize(attributes)
|
7
9
|
@attributes = attributes
|
8
10
|
end
|
@@ -44,9 +46,7 @@ module Ferrum
|
|
44
46
|
end
|
45
47
|
|
46
48
|
def expires
|
47
|
-
if @attributes["expires"]
|
48
|
-
Time.at(@attributes["expires"])
|
49
|
-
end
|
49
|
+
Time.at(@attributes["expires"]) if @attributes["expires"].positive?
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
@@ -56,24 +56,25 @@ module Ferrum
|
|
56
56
|
|
57
57
|
def all
|
58
58
|
cookies = @page.command("Network.getAllCookies")["cookies"]
|
59
|
-
cookies.
|
59
|
+
cookies.to_h { |c| [c["name"], Cookie.new(c)] }
|
60
60
|
end
|
61
61
|
|
62
62
|
def [](name)
|
63
63
|
all[name]
|
64
64
|
end
|
65
65
|
|
66
|
-
def set(
|
67
|
-
cookie =
|
68
|
-
|
69
|
-
|
66
|
+
def set(options)
|
67
|
+
cookie = (
|
68
|
+
options.is_a?(Cookie) ? options.attributes : options
|
69
|
+
).dup.transform_keys(&:to_sym)
|
70
|
+
|
70
71
|
cookie[:domain] ||= default_domain
|
71
72
|
|
72
73
|
cookie[:httpOnly] = cookie.delete(:httponly) if cookie.key?(:httponly)
|
73
74
|
cookie[:sameSite] = cookie.delete(:samesite) if cookie.key?(:samesite)
|
74
75
|
|
75
76
|
expires = cookie.delete(:expires).to_i
|
76
|
-
cookie[:expires] = expires if expires
|
77
|
+
cookie[:expires] = expires if expires.positive?
|
77
78
|
|
78
79
|
@page.command("Network.setCookie", **cookie)["success"]
|
79
80
|
end
|