ferrum 0.10.1 → 0.12
Sign up to get free protection for your applications and to get access to all the features.
- 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
|