ferrum 0.14 → 0.15
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 +277 -154
- data/lib/ferrum/browser/command.rb +4 -0
- data/lib/ferrum/browser/options/chrome.rb +8 -3
- data/lib/ferrum/browser/options.rb +38 -25
- data/lib/ferrum/browser/process.rb +43 -16
- data/lib/ferrum/browser.rb +26 -50
- data/lib/ferrum/client/subscriber.rb +76 -0
- data/lib/ferrum/{browser → client}/web_socket.rb +35 -21
- data/lib/ferrum/client.rb +169 -0
- data/lib/ferrum/context.rb +19 -15
- data/lib/ferrum/contexts.rb +46 -12
- data/lib/ferrum/cookies.rb +1 -1
- data/lib/ferrum/downloads.rb +60 -0
- data/lib/ferrum/errors.rb +2 -1
- data/lib/ferrum/headers.rb +1 -1
- data/lib/ferrum/network/exchange.rb +10 -1
- data/lib/ferrum/network/intercepted_request.rb +5 -5
- data/lib/ferrum/network/request.rb +9 -0
- data/lib/ferrum/network.rb +11 -9
- data/lib/ferrum/page/frames.rb +5 -5
- data/lib/ferrum/page/screenshot.rb +36 -24
- data/lib/ferrum/page.rb +177 -119
- data/lib/ferrum/proxy.rb +1 -1
- data/lib/ferrum/target.rb +25 -5
- data/lib/ferrum/utils/elapsed_time.rb +0 -2
- data/lib/ferrum/utils/event.rb +19 -0
- data/lib/ferrum/utils/platform.rb +4 -0
- data/lib/ferrum/utils/thread.rb +18 -0
- data/lib/ferrum/version.rb +1 -1
- data/lib/ferrum.rb +3 -0
- metadata +14 -17
- data/lib/ferrum/browser/client.rb +0 -103
- data/lib/ferrum/browser/subscriber.rb +0 -36
data/lib/ferrum/target.rb
CHANGED
|
@@ -8,17 +8,21 @@ module Ferrum
|
|
|
8
8
|
# where we enhance page class and build page ourselves.
|
|
9
9
|
attr_writer :page
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
attr_reader :session_id, :options
|
|
12
|
+
|
|
13
|
+
def initialize(browser_client, session_id = nil, params = nil)
|
|
12
14
|
@page = nil
|
|
13
|
-
@
|
|
15
|
+
@session_id = session_id
|
|
14
16
|
@params = params
|
|
17
|
+
@browser_client = browser_client
|
|
18
|
+
@options = browser_client.options
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
def update(params)
|
|
18
|
-
@params
|
|
22
|
+
@params.merge!(params)
|
|
19
23
|
end
|
|
20
24
|
|
|
21
|
-
def
|
|
25
|
+
def connected?
|
|
22
26
|
!!@page
|
|
23
27
|
end
|
|
24
28
|
|
|
@@ -26,9 +30,13 @@ module Ferrum
|
|
|
26
30
|
@page ||= build_page
|
|
27
31
|
end
|
|
28
32
|
|
|
33
|
+
def client
|
|
34
|
+
@client ||= build_client
|
|
35
|
+
end
|
|
36
|
+
|
|
29
37
|
def build_page(**options)
|
|
30
38
|
maybe_sleep_if_new_window
|
|
31
|
-
Page.new(
|
|
39
|
+
Page.new(client, context_id: context_id, target_id: id, **options)
|
|
32
40
|
end
|
|
33
41
|
|
|
34
42
|
def id
|
|
@@ -63,5 +71,17 @@ module Ferrum
|
|
|
63
71
|
# Dirty hack because new window doesn't have events at all
|
|
64
72
|
sleep(NEW_WINDOW_WAIT) if window?
|
|
65
73
|
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def build_client
|
|
78
|
+
return @browser_client.session(session_id) if options.flatten
|
|
79
|
+
|
|
80
|
+
Client.new(ws_url, options)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def ws_url
|
|
84
|
+
@browser_client.ws_url.merge(path: "/devtools/page/#{id}")
|
|
85
|
+
end
|
|
66
86
|
end
|
|
67
87
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ferrum
|
|
4
|
+
module Utils
|
|
5
|
+
class Event < Concurrent::Event
|
|
6
|
+
def iteration
|
|
7
|
+
synchronize { @iteration }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def reset
|
|
11
|
+
synchronize do
|
|
12
|
+
@iteration += 1
|
|
13
|
+
@set = false if @set
|
|
14
|
+
@iteration
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ferrum
|
|
4
|
+
module Utils
|
|
5
|
+
module Thread
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def spawn(abort_on_exception: true)
|
|
9
|
+
::Thread.new(abort_on_exception) do |whether_abort_on_exception|
|
|
10
|
+
::Thread.current.abort_on_exception = whether_abort_on_exception
|
|
11
|
+
::Thread.current.report_on_exception = true if ::Thread.current.respond_to?(:report_on_exception=)
|
|
12
|
+
|
|
13
|
+
yield
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/ferrum/version.rb
CHANGED
data/lib/ferrum.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ferrum
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: '0.
|
|
4
|
+
version: '0.15'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dmitry Vorotilin
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2024-02-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: addressable
|
|
@@ -56,22 +56,16 @@ dependencies:
|
|
|
56
56
|
name: websocket-driver
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
|
-
- - "
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0.6'
|
|
62
|
-
- - "<"
|
|
59
|
+
- - "~>"
|
|
63
60
|
- !ruby/object:Gem::Version
|
|
64
|
-
version: '0.
|
|
61
|
+
version: '0.7'
|
|
65
62
|
type: :runtime
|
|
66
63
|
prerelease: false
|
|
67
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
68
65
|
requirements:
|
|
69
|
-
- - "
|
|
70
|
-
- !ruby/object:Gem::Version
|
|
71
|
-
version: '0.6'
|
|
72
|
-
- - "<"
|
|
66
|
+
- - "~>"
|
|
73
67
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: '0.
|
|
68
|
+
version: '0.7'
|
|
75
69
|
description: Ferrum allows you to control headless Chrome browser
|
|
76
70
|
email:
|
|
77
71
|
- d.vorotilin@gmail.com
|
|
@@ -84,22 +78,23 @@ files:
|
|
|
84
78
|
- lib/ferrum.rb
|
|
85
79
|
- lib/ferrum/browser.rb
|
|
86
80
|
- lib/ferrum/browser/binary.rb
|
|
87
|
-
- lib/ferrum/browser/client.rb
|
|
88
81
|
- lib/ferrum/browser/command.rb
|
|
89
82
|
- lib/ferrum/browser/options.rb
|
|
90
83
|
- lib/ferrum/browser/options/base.rb
|
|
91
84
|
- lib/ferrum/browser/options/chrome.rb
|
|
92
85
|
- lib/ferrum/browser/options/firefox.rb
|
|
93
86
|
- lib/ferrum/browser/process.rb
|
|
94
|
-
- lib/ferrum/browser/subscriber.rb
|
|
95
87
|
- lib/ferrum/browser/version_info.rb
|
|
96
|
-
- lib/ferrum/browser/web_socket.rb
|
|
97
88
|
- lib/ferrum/browser/xvfb.rb
|
|
89
|
+
- lib/ferrum/client.rb
|
|
90
|
+
- lib/ferrum/client/subscriber.rb
|
|
91
|
+
- lib/ferrum/client/web_socket.rb
|
|
98
92
|
- lib/ferrum/context.rb
|
|
99
93
|
- lib/ferrum/contexts.rb
|
|
100
94
|
- lib/ferrum/cookies.rb
|
|
101
95
|
- lib/ferrum/cookies/cookie.rb
|
|
102
96
|
- lib/ferrum/dialog.rb
|
|
97
|
+
- lib/ferrum/downloads.rb
|
|
103
98
|
- lib/ferrum/errors.rb
|
|
104
99
|
- lib/ferrum/frame.rb
|
|
105
100
|
- lib/ferrum/frame/dom.rb
|
|
@@ -128,7 +123,9 @@ files:
|
|
|
128
123
|
- lib/ferrum/target.rb
|
|
129
124
|
- lib/ferrum/utils/attempt.rb
|
|
130
125
|
- lib/ferrum/utils/elapsed_time.rb
|
|
126
|
+
- lib/ferrum/utils/event.rb
|
|
131
127
|
- lib/ferrum/utils/platform.rb
|
|
128
|
+
- lib/ferrum/utils/thread.rb
|
|
132
129
|
- lib/ferrum/version.rb
|
|
133
130
|
homepage: https://github.com/rubycdp/ferrum
|
|
134
131
|
licenses:
|
|
@@ -148,14 +145,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
148
145
|
requirements:
|
|
149
146
|
- - ">="
|
|
150
147
|
- !ruby/object:Gem::Version
|
|
151
|
-
version: 2.
|
|
148
|
+
version: 2.7.0
|
|
152
149
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
153
150
|
requirements:
|
|
154
151
|
- - ">="
|
|
155
152
|
- !ruby/object:Gem::Version
|
|
156
153
|
version: '0'
|
|
157
154
|
requirements: []
|
|
158
|
-
rubygems_version: 3.
|
|
155
|
+
rubygems_version: 3.5.6
|
|
159
156
|
signing_key:
|
|
160
157
|
specification_version: 4
|
|
161
158
|
summary: Ruby headless Chrome driver
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "ferrum/browser/subscriber"
|
|
4
|
-
require "ferrum/browser/web_socket"
|
|
5
|
-
|
|
6
|
-
module Ferrum
|
|
7
|
-
class Browser
|
|
8
|
-
class Client
|
|
9
|
-
INTERRUPTIONS = %w[Fetch.requestPaused Fetch.authRequired].freeze
|
|
10
|
-
|
|
11
|
-
def initialize(ws_url, connectable, logger: nil, ws_max_receive_size: nil, id_starts_with: 0)
|
|
12
|
-
@connectable = connectable
|
|
13
|
-
@command_id = id_starts_with
|
|
14
|
-
@pendings = Concurrent::Hash.new
|
|
15
|
-
@ws = WebSocket.new(ws_url, ws_max_receive_size, logger)
|
|
16
|
-
@subscriber, @interrupter = Subscriber.build(2)
|
|
17
|
-
|
|
18
|
-
@thread = Thread.new do
|
|
19
|
-
Thread.current.abort_on_exception = true
|
|
20
|
-
Thread.current.report_on_exception = true if Thread.current.respond_to?(:report_on_exception=)
|
|
21
|
-
|
|
22
|
-
loop do
|
|
23
|
-
message = @ws.messages.pop
|
|
24
|
-
break unless message
|
|
25
|
-
|
|
26
|
-
if INTERRUPTIONS.include?(message["method"])
|
|
27
|
-
@interrupter.async.call(message)
|
|
28
|
-
elsif message.key?("method")
|
|
29
|
-
@subscriber.async.call(message)
|
|
30
|
-
else
|
|
31
|
-
@pendings[message["id"]]&.set(message)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def command(method, params = {})
|
|
38
|
-
pending = Concurrent::IVar.new
|
|
39
|
-
message = build_message(method, params)
|
|
40
|
-
@pendings[message[:id]] = pending
|
|
41
|
-
@ws.send_message(message)
|
|
42
|
-
data = pending.value!(@connectable.timeout)
|
|
43
|
-
@pendings.delete(message[:id])
|
|
44
|
-
|
|
45
|
-
raise DeadBrowserError if data.nil? && @ws.messages.closed?
|
|
46
|
-
raise TimeoutError unless data
|
|
47
|
-
|
|
48
|
-
error, response = data.values_at("error", "result")
|
|
49
|
-
raise_browser_error(error) if error
|
|
50
|
-
response
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def on(event, &block)
|
|
54
|
-
case event
|
|
55
|
-
when *INTERRUPTIONS
|
|
56
|
-
@interrupter.on(event, &block)
|
|
57
|
-
else
|
|
58
|
-
@subscriber.on(event, &block)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def subscribed?(event)
|
|
63
|
-
[@interrupter, @subscriber].any? { |s| s.subscribed?(event) }
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def close
|
|
67
|
-
@ws.close
|
|
68
|
-
# Give a thread some time to handle a tail of messages
|
|
69
|
-
@pendings.clear
|
|
70
|
-
@thread.kill unless @thread.join(1)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
private
|
|
74
|
-
|
|
75
|
-
def build_message(method, params)
|
|
76
|
-
{ method: method, params: params }.merge(id: next_command_id)
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def next_command_id
|
|
80
|
-
@command_id += 1
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def raise_browser_error(error)
|
|
84
|
-
case error["message"]
|
|
85
|
-
# Node has disappeared while we were trying to get it
|
|
86
|
-
when "No node with given id found",
|
|
87
|
-
"Could not find node with given id",
|
|
88
|
-
"Inspected target navigated or closed"
|
|
89
|
-
raise NodeNotFoundError, error
|
|
90
|
-
# Context is lost, page is reloading
|
|
91
|
-
when "Cannot find context with specified id"
|
|
92
|
-
raise NoExecutionContextError, error
|
|
93
|
-
when "No target with given id found"
|
|
94
|
-
raise NoSuchPageError
|
|
95
|
-
when /Could not compute content quads/
|
|
96
|
-
raise CoordinatesNotFoundError
|
|
97
|
-
else
|
|
98
|
-
raise BrowserError, error
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ferrum
|
|
4
|
-
class Browser
|
|
5
|
-
class Subscriber
|
|
6
|
-
include Concurrent::Async
|
|
7
|
-
|
|
8
|
-
def self.build(size)
|
|
9
|
-
(0..size).map { new }
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def initialize
|
|
13
|
-
super
|
|
14
|
-
@on = Concurrent::Hash.new { |h, k| h[k] = Concurrent::Array.new }
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def on(event, &block)
|
|
18
|
-
@on[event] << block
|
|
19
|
-
true
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def subscribed?(event)
|
|
23
|
-
@on.key?(event)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def call(message)
|
|
27
|
-
method, params = message.values_at("method", "params")
|
|
28
|
-
total = @on[method].size
|
|
29
|
-
@on[method].each_with_index do |block, index|
|
|
30
|
-
# In case of multiple callbacks we provide current index and total
|
|
31
|
-
block.call(params, index, total)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|