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
data/lib/ferrum/network.rb
CHANGED
@@ -14,6 +14,10 @@ module Ferrum
|
|
14
14
|
RESOURCE_TYPES = %w[Document Stylesheet Image Media Font Script TextTrack
|
15
15
|
XHR Fetch EventSource WebSocket Manifest
|
16
16
|
SignedExchange Ping CSPViolationReport Other].freeze
|
17
|
+
AUTHORIZE_BLOCK_MISSING = "Block is missing, call `authorize(...) { |r| r.continue } " \
|
18
|
+
"or subscribe to `on(:request)` events before calling it"
|
19
|
+
AUTHORIZE_TYPE_WRONG = ":type should be in #{AUTHORIZE_TYPE}"
|
20
|
+
ALLOWED_CONNECTION_TYPE = %w[none cellular2g cellular3g cellular4g bluetooth ethernet wifi wimax other].freeze
|
17
21
|
|
18
22
|
attr_reader :traffic
|
19
23
|
|
@@ -21,13 +25,16 @@ module Ferrum
|
|
21
25
|
@page = page
|
22
26
|
@traffic = []
|
23
27
|
@exchange = nil
|
28
|
+
@blacklist = nil
|
29
|
+
@whitelist = nil
|
24
30
|
end
|
25
31
|
|
26
32
|
def wait_for_idle(connections: 0, duration: 0.05, timeout: @page.browser.timeout)
|
27
|
-
start =
|
33
|
+
start = Utils::ElapsedTime.monotonic_time
|
28
34
|
|
29
35
|
until idle?(connections)
|
30
|
-
raise TimeoutError if
|
36
|
+
raise TimeoutError if Utils::ElapsedTime.timeout?(start, timeout)
|
37
|
+
|
31
38
|
sleep(duration)
|
32
39
|
end
|
33
40
|
end
|
@@ -61,9 +68,7 @@ module Ferrum
|
|
61
68
|
end
|
62
69
|
|
63
70
|
def clear(type)
|
64
|
-
unless CLEAR_TYPE.include?(type)
|
65
|
-
raise ArgumentError, ":type should be in #{CLEAR_TYPE}"
|
66
|
-
end
|
71
|
+
raise ArgumentError, ":type should be in #{CLEAR_TYPE}" unless CLEAR_TYPE.include?(type)
|
67
72
|
|
68
73
|
if type == :traffic
|
69
74
|
@traffic.clear
|
@@ -74,23 +79,28 @@ module Ferrum
|
|
74
79
|
true
|
75
80
|
end
|
76
81
|
|
82
|
+
def blacklist=(patterns)
|
83
|
+
@blacklist = Array(patterns)
|
84
|
+
blacklist_subscribe
|
85
|
+
end
|
86
|
+
alias blocklist= blacklist=
|
87
|
+
|
88
|
+
def whitelist=(patterns)
|
89
|
+
@whitelist = Array(patterns)
|
90
|
+
whitelist_subscribe
|
91
|
+
end
|
92
|
+
alias allowlist= whitelist=
|
93
|
+
|
77
94
|
def intercept(pattern: "*", resource_type: nil)
|
78
95
|
pattern = { urlPattern: pattern }
|
79
|
-
if resource_type && RESOURCE_TYPES.include?(resource_type.to_s)
|
80
|
-
pattern[:resourceType] = resource_type
|
81
|
-
end
|
96
|
+
pattern[:resourceType] = resource_type if resource_type && RESOURCE_TYPES.include?(resource_type.to_s)
|
82
97
|
|
83
98
|
@page.command("Fetch.enable", handleAuthRequests: true, patterns: [pattern])
|
84
99
|
end
|
85
100
|
|
86
101
|
def authorize(user:, password:, type: :server, &block)
|
87
|
-
unless AUTHORIZE_TYPE.include?(type)
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
if !block_given? && !@page.subscribed?("Fetch.requestPaused")
|
92
|
-
raise ArgumentError, "Block is missing, call `authorize(...) { |r| r.continue } or subscribe to `on(:request)` events before calling it"
|
93
|
-
end
|
102
|
+
raise ArgumentError, AUTHORIZE_TYPE_WRONG unless AUTHORIZE_TYPE.include?(type)
|
103
|
+
raise ArgumentError, AUTHORIZE_BLOCK_MISSING if !block_given? && !@page.subscribed?("Fetch.requestPaused")
|
94
104
|
|
95
105
|
@authorized_ids ||= {}
|
96
106
|
@authorized_ids[type] ||= []
|
@@ -116,6 +126,53 @@ module Ferrum
|
|
116
126
|
end
|
117
127
|
|
118
128
|
def subscribe
|
129
|
+
subscribe_request_will_be_sent
|
130
|
+
subscribe_response_received
|
131
|
+
subscribe_loading_finished
|
132
|
+
subscribe_loading_failed
|
133
|
+
subscribe_log_entry_added
|
134
|
+
end
|
135
|
+
|
136
|
+
def authorized_response(ids, request_id, username, password)
|
137
|
+
if ids.include?(request_id)
|
138
|
+
{ response: "CancelAuth" }
|
139
|
+
elsif username && password
|
140
|
+
{ response: "ProvideCredentials",
|
141
|
+
username: username,
|
142
|
+
password: password }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def select(request_id)
|
147
|
+
@traffic.select { |e| e.id == request_id }
|
148
|
+
end
|
149
|
+
|
150
|
+
def build_exchange(id)
|
151
|
+
Network::Exchange.new(@page, id).tap { |e| @traffic << e }
|
152
|
+
end
|
153
|
+
|
154
|
+
def emulate_network_conditions(offline: false, latency: 0,
|
155
|
+
download_throughput: -1, upload_throughput: -1,
|
156
|
+
connection_type: nil)
|
157
|
+
params = {
|
158
|
+
offline: offline, latency: latency,
|
159
|
+
downloadThroughput: download_throughput,
|
160
|
+
uploadThroughput: upload_throughput
|
161
|
+
}
|
162
|
+
|
163
|
+
params[:connectionType] = connection_type if connection_type && ALLOWED_CONNECTION_TYPE.include?(connection_type)
|
164
|
+
|
165
|
+
@page.command("Network.emulateNetworkConditions", **params)
|
166
|
+
true
|
167
|
+
end
|
168
|
+
|
169
|
+
def offline_mode
|
170
|
+
emulate_network_conditions(offline: true, latency: 0, download_throughput: 0, upload_throughput: 0)
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
def subscribe_request_will_be_sent
|
119
176
|
@page.on("Network.requestWillBeSent") do |params|
|
120
177
|
request = Network::Request.new(params)
|
121
178
|
|
@@ -140,25 +197,29 @@ module Ferrum
|
|
140
197
|
|
141
198
|
exchange.request = request
|
142
199
|
|
143
|
-
if exchange.navigation_request?(@page.main_frame.id)
|
144
|
-
@exchange = exchange
|
145
|
-
end
|
200
|
+
@exchange = exchange if exchange.navigation_request?(@page.main_frame.id)
|
146
201
|
end
|
202
|
+
end
|
147
203
|
|
204
|
+
def subscribe_response_received
|
148
205
|
@page.on("Network.responseReceived") do |params|
|
149
|
-
|
206
|
+
exchange = select(params["requestId"]).last
|
207
|
+
|
208
|
+
if exchange
|
150
209
|
response = Network::Response.new(@page, params)
|
151
210
|
exchange.response = response
|
152
211
|
end
|
153
212
|
end
|
213
|
+
end
|
154
214
|
|
215
|
+
def subscribe_loading_finished
|
155
216
|
@page.on("Network.loadingFinished") do |params|
|
156
217
|
exchange = select(params["requestId"]).last
|
157
|
-
|
158
|
-
exchange.response.body_size = params["encodedDataLength"]
|
159
|
-
end
|
218
|
+
exchange.response.body_size = params["encodedDataLength"] if exchange&.response
|
160
219
|
end
|
220
|
+
end
|
161
221
|
|
222
|
+
def subscribe_loading_failed
|
162
223
|
@page.on("Network.loadingFailed") do |params|
|
163
224
|
exchange = select(params["requestId"]).last
|
164
225
|
exchange.error ||= Network::Error.new
|
@@ -169,7 +230,9 @@ module Ferrum
|
|
169
230
|
exchange.error.monotonic_time = params["timestamp"]
|
170
231
|
exchange.error.canceled = params["canceled"]
|
171
232
|
end
|
233
|
+
end
|
172
234
|
|
235
|
+
def subscribe_log_entry_added
|
173
236
|
@page.on("Log.entryAdded") do |params|
|
174
237
|
entry = params["entry"] || {}
|
175
238
|
if entry["source"] == "network" && entry["level"] == "error"
|
@@ -184,24 +247,50 @@ module Ferrum
|
|
184
247
|
end
|
185
248
|
end
|
186
249
|
|
187
|
-
def
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
250
|
+
def blacklist_subscribe
|
251
|
+
return unless blacklist?
|
252
|
+
raise ArgumentError, "You can't use blacklist along with whitelist" if whitelist?
|
253
|
+
|
254
|
+
@blacklist_subscribe ||= begin
|
255
|
+
intercept
|
256
|
+
|
257
|
+
@page.on(:request) do |request|
|
258
|
+
if @blacklist.any? { |p| request.match?(p) }
|
259
|
+
request.abort
|
260
|
+
else
|
261
|
+
request.continue
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
true
|
196
266
|
end
|
197
267
|
end
|
198
268
|
|
199
|
-
def
|
200
|
-
|
269
|
+
def whitelist_subscribe
|
270
|
+
return unless whitelist?
|
271
|
+
raise ArgumentError, "You can't use whitelist along with blacklist" if blacklist?
|
272
|
+
|
273
|
+
@whitelist_subscribe ||= begin
|
274
|
+
intercept
|
275
|
+
|
276
|
+
@page.on(:request) do |request|
|
277
|
+
if @whitelist.any? { |p| request.match?(p) }
|
278
|
+
request.continue
|
279
|
+
else
|
280
|
+
request.abort
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
true
|
285
|
+
end
|
201
286
|
end
|
202
287
|
|
203
|
-
def
|
204
|
-
|
288
|
+
def blacklist?
|
289
|
+
Array(@blacklist).any?
|
290
|
+
end
|
291
|
+
|
292
|
+
def whitelist?
|
293
|
+
Array(@whitelist).any?
|
205
294
|
end
|
206
295
|
end
|
207
296
|
end
|
data/lib/ferrum/node.rb
CHANGED
@@ -2,15 +2,16 @@
|
|
2
2
|
|
3
3
|
module Ferrum
|
4
4
|
class Node
|
5
|
-
|
6
|
-
|
5
|
+
MOVING_WAIT_DELAY = ENV.fetch("FERRUM_NODE_MOVING_WAIT", 0.01).to_f
|
6
|
+
MOVING_WAIT_ATTEMPTS = ENV.fetch("FERRUM_NODE_MOVING_ATTEMPTS", 50).to_i
|
7
7
|
|
8
8
|
attr_reader :page, :target_id, :node_id, :description, :tag_name
|
9
9
|
|
10
10
|
def initialize(frame, target_id, node_id, description)
|
11
11
|
@page = frame.page
|
12
12
|
@target_id = target_id
|
13
|
-
@node_id
|
13
|
+
@node_id = node_id
|
14
|
+
@description = description
|
14
15
|
@tag_name = description["nodeName"].downcase
|
15
16
|
end
|
16
17
|
|
@@ -30,6 +31,27 @@ module Ferrum
|
|
30
31
|
tap { page.command("DOM.focus", slowmoable: true, nodeId: node_id) }
|
31
32
|
end
|
32
33
|
|
34
|
+
def focusable?
|
35
|
+
focus
|
36
|
+
true
|
37
|
+
rescue BrowserError => e
|
38
|
+
e.message == "Element is not focusable" ? false : raise
|
39
|
+
end
|
40
|
+
|
41
|
+
def wait_for_stop_moving(delay: MOVING_WAIT_DELAY, attempts: MOVING_WAIT_ATTEMPTS)
|
42
|
+
Utils::Attempt.with_retry(errors: NodeMovingError, max: attempts, wait: 0) do
|
43
|
+
previous, current = content_quads_with(delay: delay)
|
44
|
+
raise NodeMovingError.new(self, previous, current) if previous != current
|
45
|
+
|
46
|
+
current
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def moving?(delay: MOVING_WAIT_DELAY)
|
51
|
+
previous, current = content_quads_with(delay: delay)
|
52
|
+
previous == current
|
53
|
+
end
|
54
|
+
|
33
55
|
def blur
|
34
56
|
tap { evaluate("this.blur()") }
|
35
57
|
end
|
@@ -102,17 +124,52 @@ module Ferrum
|
|
102
124
|
def property(name)
|
103
125
|
evaluate("this['#{name}']")
|
104
126
|
end
|
127
|
+
alias [] property
|
105
128
|
|
106
129
|
def attribute(name)
|
107
130
|
evaluate("this.getAttribute('#{name}')")
|
108
131
|
end
|
109
132
|
|
133
|
+
def selected
|
134
|
+
function = <<~JS
|
135
|
+
function(element) {
|
136
|
+
if (element.nodeName.toLowerCase() !== 'select') {
|
137
|
+
throw new Error('Element is not a <select> element.');
|
138
|
+
}
|
139
|
+
return Array.from(element).filter(option => option.selected);
|
140
|
+
}
|
141
|
+
JS
|
142
|
+
page.evaluate_func(function, self, on: self)
|
143
|
+
end
|
144
|
+
|
145
|
+
def select(*values, by: :value)
|
146
|
+
tap do
|
147
|
+
function = <<~JS
|
148
|
+
function(element, values, by) {
|
149
|
+
if (element.nodeName.toLowerCase() !== 'select') {
|
150
|
+
throw new Error('Element is not a <select> element.');
|
151
|
+
}
|
152
|
+
const options = Array.from(element.options);
|
153
|
+
element.value = undefined;
|
154
|
+
for (const option of options) {
|
155
|
+
option.selected = values.some((value) => option[by] === value);
|
156
|
+
if (option.selected && !element.multiple) break;
|
157
|
+
}
|
158
|
+
element.dispatchEvent(new Event('input', { bubbles: true }));
|
159
|
+
element.dispatchEvent(new Event('change', { bubbles: true }));
|
160
|
+
}
|
161
|
+
JS
|
162
|
+
page.evaluate_func(function, self, values.flatten, by, on: self)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
110
166
|
def evaluate(expression)
|
111
167
|
page.evaluate_on(node: self, expression: expression)
|
112
168
|
end
|
113
169
|
|
114
170
|
def ==(other)
|
115
171
|
return false unless other.is_a?(Node)
|
172
|
+
|
116
173
|
# We compare backendNodeId because once nodeId is sent to frontend backend
|
117
174
|
# never returns same nodeId sending 0. In other words frontend is
|
118
175
|
# responsible for keeping track of node ids.
|
@@ -124,44 +181,45 @@ module Ferrum
|
|
124
181
|
end
|
125
182
|
|
126
183
|
def find_position(x: nil, y: nil, position: :top)
|
127
|
-
|
128
|
-
|
129
|
-
# FIXME: Case when a few quads returned
|
130
|
-
points = Ferrum.with_attempts(errors: NodeIsMovingError, max: MOVING_ATTEMPTS, wait: 0) do
|
131
|
-
sleep(MOVING_WAIT)
|
132
|
-
current = get_content_quads
|
133
|
-
|
134
|
-
if current != prev
|
135
|
-
error = NodeIsMovingError.new(self, prev, current)
|
136
|
-
prev = current
|
137
|
-
raise(error)
|
138
|
-
end
|
139
|
-
|
140
|
-
current
|
141
|
-
end.map { |q| to_points(q) }.first
|
142
|
-
|
184
|
+
points = wait_for_stop_moving.map { |q| to_points(q) }.first
|
143
185
|
get_position(points, x, y, position)
|
144
|
-
rescue
|
145
|
-
|
186
|
+
rescue CoordinatesNotFoundError
|
187
|
+
x, y = bounding_rect_coordinates
|
188
|
+
raise if x.zero? && y.zero?
|
146
189
|
|
147
|
-
|
190
|
+
[x, y]
|
191
|
+
end
|
192
|
+
|
193
|
+
# Returns a hash of the computed styles for the node
|
194
|
+
def computed_style
|
195
|
+
page
|
196
|
+
.command("CSS.getComputedStyleForNode", nodeId: node_id)["computedStyle"]
|
197
|
+
.each_with_object({}) { |style, memo| memo.merge!(style["name"] => style["value"]) }
|
148
198
|
end
|
149
199
|
|
150
200
|
private
|
151
201
|
|
152
|
-
def
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
202
|
+
def bounding_rect_coordinates
|
203
|
+
evaluate <<~JS
|
204
|
+
[this.getBoundingClientRect().left + window.pageXOffset + (this.offsetWidth / 2),
|
205
|
+
this.getBoundingClientRect().top + window.pageYOffset + (this.offsetHeight / 2)]
|
206
|
+
JS
|
157
207
|
end
|
158
208
|
|
159
|
-
def
|
209
|
+
def content_quads
|
160
210
|
quads = page.command("DOM.getContentQuads", nodeId: node_id)["quads"]
|
161
|
-
raise "Node is either not visible or not an HTMLElement" if quads.size
|
211
|
+
raise CoordinatesNotFoundError, "Node is either not visible or not an HTMLElement" if quads.size.zero?
|
212
|
+
|
162
213
|
quads
|
163
214
|
end
|
164
215
|
|
216
|
+
def content_quads_with(delay: MOVING_WAIT_DELAY)
|
217
|
+
previous = content_quads
|
218
|
+
sleep(delay)
|
219
|
+
current = content_quads
|
220
|
+
[previous, current]
|
221
|
+
end
|
222
|
+
|
165
223
|
def get_position(points, offset_x, offset_y, position)
|
166
224
|
x = y = nil
|
167
225
|
|
@@ -170,28 +228,28 @@ module Ferrum
|
|
170
228
|
x = point[:x] + offset_x.to_i
|
171
229
|
y = point[:y] + offset_y.to_i
|
172
230
|
else
|
173
|
-
x, y = points.inject([0, 0]) do |memo,
|
174
|
-
[memo[0] +
|
175
|
-
memo[1] +
|
231
|
+
x, y = points.inject([0, 0]) do |memo, coordinate|
|
232
|
+
[memo[0] + coordinate[:x],
|
233
|
+
memo[1] + coordinate[:y]]
|
176
234
|
end
|
177
235
|
|
178
|
-
x
|
179
|
-
y
|
236
|
+
x /= 4
|
237
|
+
y /= 4
|
180
238
|
end
|
181
239
|
|
182
240
|
if offset_x && offset_y && position == :center
|
183
|
-
x
|
184
|
-
y
|
241
|
+
x += offset_x.to_i
|
242
|
+
y += offset_y.to_i
|
185
243
|
end
|
186
244
|
|
187
245
|
[x, y]
|
188
246
|
end
|
189
247
|
|
190
248
|
def to_points(quad)
|
191
|
-
[{x: quad[0], y: quad[1]},
|
192
|
-
{x: quad[2], y: quad[3]},
|
193
|
-
{x: quad[4], y: quad[5]},
|
194
|
-
{x: quad[6], y: quad[7]}]
|
249
|
+
[{ x: quad[0], y: quad[1] },
|
250
|
+
{ x: quad[2], y: quad[3] },
|
251
|
+
{ x: quad[4], y: quad[5] },
|
252
|
+
{ x: quad[6], y: quad[7] }]
|
195
253
|
end
|
196
254
|
end
|
197
255
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ferrum
|
4
|
+
class Page
|
5
|
+
module Animation
|
6
|
+
def playback_rate
|
7
|
+
command("Animation.getPlaybackRate")["playbackRate"]
|
8
|
+
end
|
9
|
+
|
10
|
+
def playback_rate=(value)
|
11
|
+
command("Animation.setPlaybackRate", playbackRate: value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/ferrum/page/frames.rb
CHANGED
@@ -11,35 +11,60 @@ module Ferrum
|
|
11
11
|
@frames.values
|
12
12
|
end
|
13
13
|
|
14
|
-
def frame_by(id: nil, name: nil)
|
14
|
+
def frame_by(id: nil, name: nil, execution_id: nil)
|
15
15
|
if id
|
16
16
|
@frames[id]
|
17
17
|
elsif name
|
18
18
|
frames.find { |f| f.name == name }
|
19
|
+
elsif execution_id
|
20
|
+
frames.find { |f| f.execution_id == execution_id }
|
19
21
|
else
|
20
22
|
raise ArgumentError
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
26
|
def frames_subscribe
|
27
|
+
subscribe_frame_attached
|
28
|
+
subscribe_frame_started_loading
|
29
|
+
subscribe_frame_navigated
|
30
|
+
subscribe_frame_stopped_loading
|
31
|
+
|
32
|
+
subscribe_navigated_within_document
|
33
|
+
|
34
|
+
subscribe_request_will_be_sent
|
35
|
+
|
36
|
+
subscribe_execution_context_created
|
37
|
+
subscribe_execution_context_destroyed
|
38
|
+
subscribe_execution_contexts_cleared
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def subscribe_frame_attached
|
25
44
|
on("Page.frameAttached") do |params|
|
26
45
|
parent_frame_id, frame_id = params.values_at("parentFrameId", "frameId")
|
27
46
|
@frames[frame_id] = Frame.new(frame_id, self, parent_frame_id)
|
28
47
|
end
|
48
|
+
end
|
29
49
|
|
50
|
+
def subscribe_frame_started_loading
|
30
51
|
on("Page.frameStartedLoading") do |params|
|
31
52
|
frame = @frames[params["frameId"]]
|
32
53
|
frame.state = :started_loading
|
33
54
|
@event.reset
|
34
55
|
end
|
56
|
+
end
|
35
57
|
|
58
|
+
def subscribe_frame_navigated
|
36
59
|
on("Page.frameNavigated") do |params|
|
37
60
|
frame_id, name = params["frame"]&.values_at("id", "name")
|
38
61
|
frame = @frames[frame_id]
|
39
62
|
frame.state = :navigated
|
40
63
|
frame.name = name unless name.to_s.empty?
|
41
64
|
end
|
65
|
+
end
|
42
66
|
|
67
|
+
def subscribe_frame_stopped_loading
|
43
68
|
on("Page.frameStoppedLoading") do |params|
|
44
69
|
# `DOM.performSearch` doesn't work without getting #document node first.
|
45
70
|
# It returns node with nodeId 1 and nodeType 9 from which descend the
|
@@ -47,7 +72,7 @@ module Ferrum
|
|
47
72
|
# node will change the id and all subsequent nodes have to change id too.
|
48
73
|
if @main_frame.id == params["frameId"]
|
49
74
|
@event.set if idling?
|
50
|
-
|
75
|
+
document_node_id
|
51
76
|
end
|
52
77
|
|
53
78
|
frame = @frames[params["frameId"]]
|
@@ -55,21 +80,25 @@ module Ferrum
|
|
55
80
|
|
56
81
|
@event.set if idling?
|
57
82
|
end
|
83
|
+
end
|
58
84
|
|
85
|
+
def subscribe_navigated_within_document
|
59
86
|
on("Page.navigatedWithinDocument") do
|
60
87
|
@event.set if idling?
|
61
88
|
end
|
89
|
+
end
|
62
90
|
|
91
|
+
def subscribe_request_will_be_sent
|
63
92
|
on("Network.requestWillBeSent") do |params|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
@event.reset if params["type"] == "Document"
|
70
|
-
end
|
93
|
+
# Possible types:
|
94
|
+
# Document, Stylesheet, Image, Media, Font, Script, TextTrack, XHR,
|
95
|
+
# Fetch, EventSource, WebSocket, Manifest, SignedExchange, Ping,
|
96
|
+
# CSPViolationReport, Other
|
97
|
+
@event.reset if params["frameId"] == @main_frame.id && params["type"] == "Document"
|
71
98
|
end
|
99
|
+
end
|
72
100
|
|
101
|
+
def subscribe_execution_context_created
|
73
102
|
on("Runtime.executionContextCreated") do |params|
|
74
103
|
context_id = params.dig("context", "id")
|
75
104
|
frame_id = params.dig("context", "auxData", "frameId")
|
@@ -83,25 +112,27 @@ module Ferrum
|
|
83
112
|
end
|
84
113
|
|
85
114
|
frame = @frames[frame_id] || Frame.new(frame_id, self)
|
86
|
-
frame.
|
115
|
+
frame.execution_id = context_id
|
87
116
|
|
88
117
|
@frames[frame_id] ||= frame
|
89
118
|
end
|
119
|
+
end
|
90
120
|
|
121
|
+
def subscribe_execution_context_destroyed
|
91
122
|
on("Runtime.executionContextDestroyed") do |params|
|
92
123
|
execution_id = params["executionContextId"]
|
93
|
-
frame =
|
94
|
-
frame
|
124
|
+
frame = frame_by(execution_id: execution_id)
|
125
|
+
frame&.execution_id = nil
|
95
126
|
end
|
127
|
+
end
|
96
128
|
|
129
|
+
def subscribe_execution_contexts_cleared
|
97
130
|
on("Runtime.executionContextsCleared") do
|
98
131
|
@frames.delete_if { |_, f| !f.main? }
|
99
|
-
@main_frame.
|
132
|
+
@main_frame.execution_id = nil
|
100
133
|
end
|
101
134
|
end
|
102
135
|
|
103
|
-
private
|
104
|
-
|
105
136
|
def idling?
|
106
137
|
@frames.all? { |_, f| f.state == :stopped_loading }
|
107
138
|
end
|